mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-22 10:16:01 +08:00
[c] Codegen improvements.
This commit is contained in:
parent
759434e461
commit
4fd23d3abe
3
.gitignore
vendored
3
.gitignore
vendored
@ -241,4 +241,5 @@ out.json
|
|||||||
spine-cpp/extraction.log
|
spine-cpp/extraction.log
|
||||||
extraction.log
|
extraction.log
|
||||||
spine-c-new/codegen/dist
|
spine-c-new/codegen/dist
|
||||||
spine-c-new/codegen/all-spine-types.json
|
spine-c-new/codegen/all-spine-types.json
|
||||||
|
spine-c-new/codegen/spine-cpp-types.json
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
- We must review any and all code in the spine-c-new codegen
|
|
||||||
- Read README.md in spine-c-new/codegen
|
|
||||||
- find the main entry point, then
|
|
||||||
- Generate language bindings from spine-cpp-lite
|
- Generate language bindings from spine-cpp-lite
|
||||||
- Create a TypeScript-based generator that takes spine-cpp-lite.h and generates:
|
- Create a TypeScript-based generator that takes spine-cpp-lite.h and generates:
|
||||||
- Swift bindings (matching current spine-ios output from spine-cpp-lite-codegen.py)
|
- Swift bindings (matching current spine-ios output from spine-cpp-lite-codegen.py)
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
# spine-c-new codegen should not special case spine_bool but use stdbool instead
|
|
||||||
|
|
||||||
**Status:** Refining
|
|
||||||
**Created:** 2025-07-08T22:03:22
|
|
||||||
**Agent PID:** 23353
|
|
||||||
|
|
||||||
## Original Todo
|
|
||||||
- spine-c-new codegen should not special case spine_bool but use stdbool instead.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
The spine-c-new codegen currently defines `spine_bool` as `int32_t` for C compatibility. This task involves updating the codegen to use the standard C99 `bool` type from `<stdbool.h>` instead of the custom `spine_bool` typedef. This will make the C API more modern and standard-compliant.
|
|
||||||
|
|
||||||
Currently:
|
|
||||||
- `spine_bool` is typedef'd as `int32_t` in base.h
|
|
||||||
- The codegen maps C++ `bool` to `spine_bool` throughout
|
|
||||||
- All generated code uses `spine_bool` instead of standard `bool`
|
|
||||||
|
|
||||||
The change will:
|
|
||||||
- Use standard C99 `bool` from `<stdbool.h>`
|
|
||||||
- Remove the special-casing in the codegen
|
|
||||||
- Make the API more standards-compliant
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
- [ ] Update typedef in base.h to use stdbool.h instead of int32_t (spine-c-new/src/base.h:65)
|
|
||||||
- [ ] Update type mapping in types.ts from 'spine_bool' to 'bool' (spine-c-new/codegen/src/types.ts:70)
|
|
||||||
- [ ] Update pointer type handling for bool* in types.ts (spine-c-new/codegen/src/types.ts:148-149)
|
|
||||||
- [ ] Update reference type handling for bool& in types.ts (spine-c-new/codegen/src/types.ts:176)
|
|
||||||
- [ ] Update array element mapping in array-scanner.ts (spine-c-new/codegen/src/array-scanner.ts:103)
|
|
||||||
- [ ] Update return type check in array-generator.ts (spine-c-new/codegen/src/generators/array-generator.ts:148)
|
|
||||||
- [ ] Update default values for bool in array-generator.ts (spine-c-new/codegen/src/generators/array-generator.ts:209,216)
|
|
||||||
- [ ] Update default return value check in method-generator.ts (spine-c-new/codegen/src/generators/method-generator.ts:346)
|
|
||||||
- [ ] Run codegen to regenerate all C wrapper code
|
|
||||||
- [ ] Update test file to ensure bool types work correctly (spine-c-new/test/test.c)
|
|
||||||
- [ ] Automated test: Run codegen and verify it completes without errors
|
|
||||||
- [ ] Automated test: Verify generated code compiles with C compiler
|
|
||||||
- [ ] User test: Build spine-c-new with cmake and verify compilation succeeds
|
|
||||||
- [ ] User test: Run test.c to verify bool values work correctly (true/false instead of 1/0)
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
16
spine-c-new/build.sh
Executable file
16
spine-c-new/build.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
for arg in "${@:-clean build}"; do
|
||||||
|
case $arg in
|
||||||
|
clean) rm -rf build ;;
|
||||||
|
build)
|
||||||
|
mkdir -p build && cd build
|
||||||
|
[ -f CMakeCache.txt ] || cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
make -j$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)
|
||||||
|
cd ..
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@ -14,28 +14,28 @@ import { scanArraySpecializations } from './array-scanner';
|
|||||||
/**
|
/**
|
||||||
* Checks for methods that have both const and non-const versions with different return types.
|
* Checks for methods that have both const and non-const versions with different return types.
|
||||||
* This is a problem for C bindings because C doesn't support function overloading.
|
* This is a problem for C bindings because C doesn't support function overloading.
|
||||||
*
|
*
|
||||||
* In C++, you can have:
|
* In C++, you can have:
|
||||||
* T& getValue(); // for non-const objects
|
* T& getValue(); // for non-const objects
|
||||||
* const T& getValue() const; // for const objects
|
* const T& getValue() const; // for const objects
|
||||||
*
|
*
|
||||||
* But in C, we can only have one function with a given name, so we need to detect
|
* But in C, we can only have one function with a given name, so we need to detect
|
||||||
* and report these conflicts.
|
* and report these conflicts.
|
||||||
*
|
*
|
||||||
* @param classes - Array of class/struct types to check
|
* @param classes - Array of class/struct types to check
|
||||||
* @param exclusions - Exclusion rules to skip specific methods
|
* @param exclusions - Exclusion rules to skip specific methods
|
||||||
* @returns Array of conflicts found, or exits if conflicts exist
|
* @returns Array of conflicts found, or exits if conflicts exist
|
||||||
*/
|
*/
|
||||||
function checkConstNonConstConflicts(classes: Type[], exclusions: Exclusion[]): void {
|
function checkConstNonConstConflicts(classes: Type[], exclusions: Exclusion[]): void {
|
||||||
const conflicts: Array<{type: string, method: string}> = [];
|
const conflicts: Array<{ type: string, method: string }> = [];
|
||||||
|
|
||||||
for (const type of classes) {
|
for (const type of classes) {
|
||||||
// Get all non-static methods first
|
// Get all non-static methods first
|
||||||
const allMethods = type.members?.filter(m =>
|
const allMethods = type.members?.filter(m =>
|
||||||
m.kind === 'method' &&
|
m.kind === 'method' &&
|
||||||
!m.isStatic
|
!m.isStatic
|
||||||
);
|
);
|
||||||
|
|
||||||
if (allMethods) {
|
if (allMethods) {
|
||||||
const methodGroups = new Map<string, Member[]>();
|
const methodGroups = new Map<string, Member[]>();
|
||||||
for (const method of allMethods) {
|
for (const method of allMethods) {
|
||||||
@ -53,35 +53,35 @@ function checkConstNonConstConflicts(classes: Type[], exclusions: Exclusion[]):
|
|||||||
}
|
}
|
||||||
methodGroups.get(key)!.push(method);
|
methodGroups.get(key)!.push(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [signature, group] of methodGroups) {
|
for (const [signature, group] of methodGroups) {
|
||||||
if (group.length > 1) {
|
if (group.length > 1) {
|
||||||
const returnTypes = new Set(group.map(m => m.returnType));
|
const returnTypes = new Set(group.map(m => m.returnType));
|
||||||
if (returnTypes.size > 1) {
|
if (returnTypes.size > 1) {
|
||||||
conflicts.push({type: type.name, method: group[0].name});
|
conflicts.push({ type: type.name, method: group[0].name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found conflicts, report them all and exit
|
// If we found conflicts, report them all and exit
|
||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) {
|
||||||
console.error("\n" + "=".repeat(80));
|
console.error("\n" + "=".repeat(80));
|
||||||
console.error("SUMMARY OF ALL CONST/NON-CONST METHOD CONFLICTS");
|
console.error("SUMMARY OF ALL CONST/NON-CONST METHOD CONFLICTS");
|
||||||
console.error("=".repeat(80));
|
console.error("=".repeat(80));
|
||||||
console.error(`\nFound ${conflicts.length} method conflicts across the codebase:\n`);
|
console.error(`\nFound ${conflicts.length} method conflicts across the codebase:\n`);
|
||||||
|
|
||||||
for (const conflict of conflicts) {
|
for (const conflict of conflicts) {
|
||||||
console.error(` - ${conflict.type}::${conflict.method}()`);
|
console.error(` - ${conflict.type}::${conflict.method}()`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("\nThese methods have both const and non-const versions in C++ which cannot");
|
console.error("\nThese methods have both const and non-const versions in C++ which cannot");
|
||||||
console.error("be represented in the C API. You need to either:");
|
console.error("be represented in the C API. You need to either:");
|
||||||
console.error(" 1. Add these to exclusions.txt");
|
console.error(" 1. Add these to exclusions.txt");
|
||||||
console.error(" 2. Modify the C++ code to avoid const/non-const overloading");
|
console.error(" 2. Modify the C++ code to avoid const/non-const overloading");
|
||||||
console.error("=".repeat(80) + "\n");
|
console.error("=".repeat(80) + "\n");
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,119 +89,120 @@ function checkConstNonConstConflicts(classes: Type[], exclusions: Exclusion[]):
|
|||||||
async function main() {
|
async function main() {
|
||||||
// Extract types if needed
|
// Extract types if needed
|
||||||
extractTypes();
|
extractTypes();
|
||||||
|
|
||||||
console.log('Loading type information...');
|
console.log('Loading type information...');
|
||||||
|
|
||||||
// Load all necessary data
|
// Load all necessary data
|
||||||
const typesJson = loadTypes() as SpineTypes;
|
const typesJson = loadTypes() as SpineTypes;
|
||||||
const exclusions = loadExclusions(path.join(__dirname, '../exclusions.txt'));
|
const exclusions = loadExclusions(path.join(__dirname, '../exclusions.txt'));
|
||||||
|
|
||||||
// Flatten all types from all headers into a single array
|
// Flatten all types from all headers into a single array
|
||||||
const allTypes: Type[] = [];
|
const allTypes: Type[] = [];
|
||||||
for (const header of Object.keys(typesJson)) {
|
for (const header of Object.keys(typesJson)) {
|
||||||
allTypes.push(...typesJson[header]);
|
allTypes.push(...typesJson[header]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a map of all types for easy lookup
|
// Create a map of all types for easy lookup
|
||||||
const typeMap = new Map<string, Type>();
|
const typeMap = new Map<string, Type>();
|
||||||
for (const type of allTypes) {
|
for (const type of allTypes) {
|
||||||
typeMap.set(type.name, type);
|
typeMap.set(type.name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Filter types: only exclude templates and manually excluded types
|
// Filter types: only exclude templates and manually excluded types
|
||||||
const includedTypes = allTypes.filter(type => {
|
const includedTypes = allTypes.filter(type => {
|
||||||
if (isTypeExcluded(type.name, exclusions)) {
|
if (isTypeExcluded(type.name, exclusions)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only exclude template types
|
// Only exclude template types
|
||||||
if (type.isTemplate === true) {
|
if (type.isTemplate === true) {
|
||||||
console.log(`Auto-excluding template type: ${type.name}`);
|
console.log(`Auto-excluding template type: ${type.name}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include everything else (including abstract types)
|
// Include everything else (including abstract types)
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Separate classes and enums
|
// Separate classes and enums
|
||||||
const classes = includedTypes.filter(t => t.kind === 'class' || t.kind === 'struct');
|
const classes = includedTypes.filter(t => t.kind === 'class' || t.kind === 'struct');
|
||||||
const enums = includedTypes.filter(t => t.kind === 'enum');
|
const enums = includedTypes.filter(t => t.kind === 'enum');
|
||||||
|
|
||||||
console.log(`Found ${classes.length} classes/structs and ${enums.length} enums to generate`);
|
console.log(`Found ${classes.length} classes/structs and ${enums.length} enums to generate`);
|
||||||
|
|
||||||
|
// Check for const/non-const conflicts
|
||||||
|
checkConstNonConstConflicts(classes, exclusions);
|
||||||
|
|
||||||
// Initialize generators
|
// Initialize generators
|
||||||
const constructorGen = new ConstructorGenerator();
|
const constructorGen = new ConstructorGenerator();
|
||||||
const methodGen = new MethodGenerator(exclusions);
|
const methodGen = new MethodGenerator(exclusions);
|
||||||
const enumGen = new EnumGenerator();
|
const enumGen = new EnumGenerator();
|
||||||
const fileWriter = new FileWriter(path.join(__dirname, '../../src/generated'));
|
const fileWriter = new FileWriter(path.join(__dirname, '../../src/generated'));
|
||||||
|
|
||||||
// Check for const/non-const conflicts
|
|
||||||
checkConstNonConstConflicts(classes, exclusions);
|
|
||||||
|
|
||||||
// Generate code for each type
|
// Generate code for each type
|
||||||
for (const type of classes) {
|
for (const type of classes) {
|
||||||
console.log(`Generating ${type.name}...`);
|
console.log(`Generating ${type.name}...`);
|
||||||
|
|
||||||
const headerContent: string[] = [];
|
const headerContent: string[] = [];
|
||||||
const sourceContent: string[] = [];
|
const sourceContent: string[] = [];
|
||||||
|
|
||||||
// Source includes
|
// Source includes
|
||||||
sourceContent.push(`#include "${toSnakeCase(type.name)}.h"`);
|
sourceContent.push(`#include "${toSnakeCase(type.name)}.h"`);
|
||||||
sourceContent.push('#include <spine/spine.h>');
|
sourceContent.push('#include <spine/spine.h>');
|
||||||
sourceContent.push('');
|
sourceContent.push('');
|
||||||
sourceContent.push('using namespace spine;');
|
sourceContent.push('using namespace spine;');
|
||||||
sourceContent.push('');
|
sourceContent.push('');
|
||||||
|
|
||||||
// Opaque type is already in types.h, don't generate it here
|
// Opaque type is already in types.h, don't generate it here
|
||||||
|
|
||||||
// Generate constructors
|
// Generate constructors
|
||||||
const constructors = constructorGen.generate(type);
|
const constructors = constructorGen.generate(type);
|
||||||
if (constructors.declarations.length > 0) {
|
if (constructors.declarations.length > 0) {
|
||||||
headerContent.push(...constructors.declarations);
|
headerContent.push(...constructors.declarations);
|
||||||
sourceContent.push(...constructors.implementations);
|
sourceContent.push(...constructors.implementations);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate methods
|
// Generate methods
|
||||||
const methods = methodGen.generate(type);
|
const methods = methodGen.generate(type);
|
||||||
if (methods.declarations.length > 0) {
|
if (methods.declarations.length > 0) {
|
||||||
headerContent.push(...methods.declarations);
|
headerContent.push(...methods.declarations);
|
||||||
sourceContent.push(...methods.implementations);
|
sourceContent.push(...methods.implementations);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write files
|
// Write files
|
||||||
await fileWriter.writeType(type.name, headerContent, sourceContent);
|
await fileWriter.writeType(type.name, headerContent, sourceContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate enum files
|
// Generate enum files
|
||||||
for (const enumType of enums) {
|
for (const enumType of enums) {
|
||||||
console.log(`Generating enum ${enumType.name}...`);
|
console.log(`Generating enum ${enumType.name}...`);
|
||||||
const enumCode = enumGen.generate(enumType);
|
const enumCode = enumGen.generate(enumType);
|
||||||
await fileWriter.writeEnum(enumType.name, enumCode);
|
await fileWriter.writeEnum(enumType.name, enumCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Array specializations
|
// Generate Array specializations
|
||||||
console.log('\nScanning for Array specializations...');
|
console.log('\nScanning for Array specializations...');
|
||||||
const enumNames = new Set(enums.map(e => e.name));
|
const enumNames = new Set(enums.map(e => e.name));
|
||||||
const arraySpecs = scanArraySpecializations(typesJson, exclusions, enumNames);
|
const arraySpecs = scanArraySpecializations(typesJson, exclusions, enumNames);
|
||||||
console.log(`Found ${arraySpecs.length} array specializations to generate`);
|
console.log(`Found ${arraySpecs.length} array specializations to generate`);
|
||||||
|
|
||||||
if (arraySpecs.length > 0) {
|
if (arraySpecs.length > 0) {
|
||||||
console.log('\nGenerating arrays.h/arrays.cpp...');
|
console.log('\nGenerating arrays.h/arrays.cpp...');
|
||||||
const arrayGen = new ArrayGenerator(typesJson);
|
const arrayGen = new ArrayGenerator(typesJson);
|
||||||
const { header, source } = arrayGen.generate(arraySpecs);
|
const { header, source } = arrayGen.generate(arraySpecs);
|
||||||
|
|
||||||
// Write arrays.h and arrays.cpp
|
// Write arrays.h and arrays.cpp
|
||||||
await fileWriter.writeArrays(header, source);
|
await fileWriter.writeArrays(header, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate types.h file (includes arrays.h)
|
// Generate types.h file (includes arrays.h)
|
||||||
await fileWriter.writeTypesHeader(classes, enums);
|
await fileWriter.writeTypesHeader(classes, enums);
|
||||||
|
|
||||||
// Generate main header file
|
// Generate main header file
|
||||||
await fileWriter.writeMainHeader(classes, enums);
|
await fileWriter.writeMainHeader(classes, enums);
|
||||||
|
|
||||||
console.log('Code generation complete!');
|
console.log('Code generation complete!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user