[cpp] Various fixes for c wrapper generation

This commit is contained in:
Mario Zechner 2025-07-10 00:33:36 +02:00
parent e1577829dd
commit b1e5ed1c25
10 changed files with 104 additions and 1090 deletions

View File

@ -1,229 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import { Type, toSnakeCase } from './types';
const LICENSE_HEADER = `/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/`;
export class FileWriter {
constructor(private outputDir: string) {
// Clean and recreate output directory
this.cleanOutputDirectory();
}
private cleanOutputDirectory(): void {
// Remove existing generated directory if it exists
if (fs.existsSync(this.outputDir)) {
console.log(`Cleaning ${this.outputDir}...`);
fs.rmSync(this.outputDir, { recursive: true, force: true });
}
// Recreate the directory
fs.mkdirSync(this.outputDir, { recursive: true });
}
async writeType(typeName: string, headerContent: string[], sourceContent: string[]): Promise<void> {
const fileName = toSnakeCase(typeName);
// Write header file
const headerPath = path.join(this.outputDir, `${fileName}.h`);
const headerGuard = `SPINE_C_${typeName.toUpperCase()}_H`;
// Check if the header content uses any custom types from extensions.h
const headerString = headerContent.join('\n');
const needsExtensions = headerString.includes('spine_void') ||
headerString.includes('spine_dispose_renderer_object') ||
headerString.includes('spine_texture_loader');
const headerLines = [
LICENSE_HEADER,
'',
`#ifndef ${headerGuard}`,
`#define ${headerGuard}`,
'',
'#ifdef __cplusplus',
'extern "C" {',
'#endif',
'',
'#include "types.h"',
];
// Include extensions.h if needed
if (needsExtensions) {
headerLines.push('#include "../extensions.h"');
}
headerLines.push(
'',
...headerContent,
'',
'#ifdef __cplusplus',
'}',
'#endif',
'',
`#endif // ${headerGuard}`
);
fs.writeFileSync(headerPath, headerLines.join('\n'));
// Write source file (as .cpp since it contains C++ code)
const sourcePath = path.join(this.outputDir, `${fileName}.cpp`);
const sourceLines = [
LICENSE_HEADER,
'',
...sourceContent
];
fs.writeFileSync(sourcePath, sourceLines.join('\n'));
}
async writeEnum(enumName: string, content: string[]): Promise<void> {
const fileName = toSnakeCase(enumName);
const headerPath = path.join(this.outputDir, `${fileName}.h`);
const headerGuard = `SPINE_C_${enumName.toUpperCase()}_H`;
const headerLines = [
LICENSE_HEADER,
'',
`#ifndef ${headerGuard}`,
`#define ${headerGuard}`,
'',
'#include "../base.h"',
'',
'#ifdef __cplusplus',
'extern "C" {',
'#endif',
'',
...content,
'',
'#ifdef __cplusplus',
'}',
'#endif',
'',
`#endif // ${headerGuard}`
];
fs.writeFileSync(headerPath, headerLines.join('\n'));
}
async writeMainHeader(classes: Type[], enums: Type[]): Promise<void> {
const mainHeaderPath = path.join(this.outputDir, '..', '..', 'include', 'spine-c.h');
// Ensure include directory exists
const includeDir = path.dirname(mainHeaderPath);
if (!fs.existsSync(includeDir)) {
fs.mkdirSync(includeDir, { recursive: true });
}
const lines = [
LICENSE_HEADER,
'',
'#ifndef SPINE_C_H',
'#define SPINE_C_H',
'',
'// Base definitions',
'#include "../src/base.h"',
'',
'// All type declarations and enum includes',
'#include "../src/generated/types.h"',
'',
'// Extension functions',
'#include "../src/extensions.h"',
'',
'// Generated class types'
];
// Add class includes (they contain the actual function declarations)
for (const classType of classes) {
lines.push(`#include "../src/generated/${toSnakeCase(classType.name)}.h"`);
}
lines.push('');
lines.push('#endif // SPINE_C_H');
fs.writeFileSync(mainHeaderPath, lines.join('\n'));
}
async writeArrays(header: string[], source: string[]): Promise<void> {
const headerPath = path.join(this.outputDir, 'arrays.h');
const sourcePath = path.join(this.outputDir, 'arrays.cpp');
// Add license header to both files
const headerLines = [LICENSE_HEADER, '', ...header];
const sourceLines = [LICENSE_HEADER, '', ...source];
fs.writeFileSync(headerPath, headerLines.join('\n'));
fs.writeFileSync(sourcePath, sourceLines.join('\n'));
}
async writeTypesHeader(classes: Type[], enums: Type[]): Promise<void> {
const headerPath = path.join(this.outputDir, 'types.h');
const lines: string[] = [
LICENSE_HEADER,
'',
'#ifndef SPINE_C_TYPES_H',
'#define SPINE_C_TYPES_H',
'',
'#ifdef __cplusplus',
'extern "C" {',
'#endif',
'',
'#include "../base.h"',
'',
'// Forward declarations for all non-enum types'
];
// Forward declare all class types
for (const classType of classes) {
const cTypeName = `spine_${toSnakeCase(classType.name)}`;
lines.push(`SPINE_OPAQUE_TYPE(${cTypeName})`);
}
lines.push('');
lines.push('// Include all enum types (cannot be forward declared)');
// Include all enum headers
for (const enumType of enums) {
lines.push(`#include "${toSnakeCase(enumType.name)}.h"`);
}
lines.push('');
lines.push('// Array specializations');
lines.push('#include "arrays.h"');
lines.push('');
lines.push('#ifdef __cplusplus');
lines.push('}');
lines.push('#endif');
lines.push('');
lines.push('#endif // SPINE_C_TYPES_H');
fs.writeFileSync(headerPath, lines.join('\n'));
}
}

View File

@ -1,232 +0,0 @@
import { ArraySpecialization, ClassOrStruct, Method, toSnakeCase, Type } from '../types';
export class ArrayGenerator {
private arrayType: ClassOrStruct | undefined;
constructor(private typesJson: any) {
// Find the Array type definition
for (const header of Object.keys(typesJson)) {
const arrayType = typesJson[header].find((t: Type) => t.kind === 'class' && t.name === 'Array');
if (arrayType) {
this.arrayType = arrayType;
break;
}
}
}
/**
* Generates arrays.h and arrays.cpp content
*/
generate(specializations: ArraySpecialization[]): { header: string[], source: string[] } {
const header: string[] = [];
const source: string[] = [];
if (!this.arrayType) {
console.error('ERROR: Array type not found in spine-cpp types');
return { header, source };
}
// Header file
header.push('#ifndef SPINE_C_ARRAYS_H');
header.push('#define SPINE_C_ARRAYS_H');
header.push('');
header.push('#include "../base.h"');
header.push('#include "types.h"');
header.push('');
header.push('#ifdef __cplusplus');
header.push('extern "C" {');
header.push('#endif');
header.push('');
// Source file
source.push('#include "arrays.h"');
source.push('#include <spine/Array.h>');
source.push('#include <spine/spine.h>');
source.push('');
source.push('using namespace spine;');
source.push('');
// Generate for each specialization
for (const spec of specializations) {
console.log(`Generating array specialization: ${spec.cTypeName}`);
this.generateSpecialization(spec, header, source);
}
// Close header
header.push('#ifdef __cplusplus');
header.push('}');
header.push('#endif');
header.push('');
header.push('#endif // SPINE_C_ARRAYS_H');
return { header, source };
}
private generateSpecialization(spec: ArraySpecialization, header: string[], source: string[]) {
// Opaque type declaration
header.push(`// ${spec.cppType}`);
header.push(`SPINE_OPAQUE_TYPE(${spec.cTypeName})`);
header.push('');
// Get Array methods to wrap
const methods = this.arrayType!.members?.filter(m =>
m.kind === 'method'
).filter(m => !m.isStatic && !m.name.includes('operator')) || [];
// Generate create method (constructor)
header.push(`SPINE_C_EXPORT ${spec.cTypeName} ${spec.cTypeName}_create();`);
source.push(`${spec.cTypeName} ${spec.cTypeName}_create() {`);
source.push(` return (${spec.cTypeName}) new (__FILE__, __LINE__) ${spec.cppType}();`);
source.push('}');
source.push('');
// Generate dispose
header.push(`SPINE_C_EXPORT void ${spec.cTypeName}_dispose(${spec.cTypeName} array);`);
source.push(`void ${spec.cTypeName}_dispose(${spec.cTypeName} array) {`);
source.push(` if (!array) return;`);
source.push(` delete (${spec.cppType}*) array;`);
source.push('}');
source.push('');
// Generate hardcoded get/set methods
header.push(`SPINE_C_EXPORT ${spec.cElementType} ${spec.cTypeName}_get(${spec.cTypeName} array, int index);`);
source.push(`${spec.cElementType} ${spec.cTypeName}_get(${spec.cTypeName} array, int index) {`);
source.push(` if (!array) return ${this.getDefaultValue(spec)};`);
source.push(` ${spec.cppType} *_array = (${spec.cppType}*) array;`);
source.push(` return ${this.convertFromCpp(spec, '(*_array)[index]')};`);
source.push('}');
source.push('');
header.push(`SPINE_C_EXPORT void ${spec.cTypeName}_set(${spec.cTypeName} array, int index, ${spec.cElementType} value);`);
source.push(`void ${spec.cTypeName}_set(${spec.cTypeName} array, int index, ${spec.cElementType} value) {`);
source.push(` if (!array) return;`);
source.push(` ${spec.cppType} *_array = (${spec.cppType}*) array;`);
source.push(` (*_array)[index] = ${this.convertToCpp(spec, 'value')};`);
source.push('}');
source.push('');
// Generate wrapper for each Array method
for (const method of methods) {
this.generateMethodWrapper(spec, method, header, source);
}
header.push('');
}
private generateMethodWrapper(spec: ArraySpecialization, method: Method, header: string[], source: string[]) {
// Skip constructors and destructors
if (method.name === 'Array' || method.name === '~Array') return;
// Build C function name
const cFuncName = `${spec.cTypeName}_${toSnakeCase(method.name)}`;
// Convert return type
let returnType = 'void';
let hasReturn = false;
if (method.returnType && method.returnType !== 'void') {
hasReturn = true;
if (method.returnType === 'T &' || method.returnType === `${spec.elementType} &`) {
// Return by reference becomes return by value in C
returnType = spec.cElementType;
} else if (method.returnType === 'T *' || method.returnType === `${spec.elementType} *`) {
returnType = spec.cElementType;
} else if (method.returnType === 'size_t') {
returnType = 'size_t';
} else if (method.returnType === 'int') {
returnType = 'int';
} else if (method.returnType === 'bool') {
returnType = 'bool';
} else if (method.returnType === `${spec.cppType} *`) {
returnType = spec.cTypeName;
} else {
// Unknown return type - skip this method
console.log(` Skipping Array method ${method.name} - unknown return type: ${method.returnType}`);
return;
}
}
// Build parameter list
const cParams: string[] = [`${spec.cTypeName} array`];
const cppArgs: string[] = [];
if (method.parameters) {
for (const param of method.parameters) {
if (param.type === 'T' || param.type === spec.elementType ||
param.type === 'const T &' || param.type === `const ${spec.elementType} &`) {
cParams.push(`${spec.cElementType} ${param.name}`);
cppArgs.push(this.convertToCpp(spec, param.name));
} else if (param.type === 'size_t') {
cParams.push(`size_t ${param.name}`);
cppArgs.push(param.name);
} else if (param.type === 'int') {
cParams.push(`int ${param.name}`);
cppArgs.push(param.name);
} else {
// Unknown parameter type - skip this method
console.log(` Skipping Array method ${method.name} - unknown param type: ${param.type}`);
return;
}
}
}
// Generate declaration
header.push(`SPINE_C_EXPORT ${returnType} ${cFuncName}(${cParams.join(', ')});`);
// Generate implementation
source.push(`${returnType} ${cFuncName}(${cParams.join(', ')}) {`);
source.push(` if (!array) return${hasReturn ? ' ' + this.getDefaultReturn(returnType, spec) : ''};`);
source.push(` ${spec.cppType} *_array = (${spec.cppType}*) array;`);
const call = `_array->${method.name}(${cppArgs.join(', ')})`;
if (hasReturn) {
if (returnType === spec.cElementType) {
source.push(` return ${this.convertFromCpp(spec, call)};`);
} else {
source.push(` return ${call};`);
}
} else {
source.push(` ${call};`);
}
source.push('}');
source.push('');
}
private getDefaultValue(spec: ArraySpecialization): string {
if (spec.isPointer) return 'nullptr';
if (spec.isPrimitive) {
if (spec.cElementType === 'bool') return 'false';
return '0';
}
if (spec.isEnum) return '0';
return '0';
}
private getDefaultReturn(returnType: string, spec: ArraySpecialization): string {
if (returnType === 'bool') return 'false';
if (returnType === 'size_t' || returnType === 'int') return '0';
if (returnType === spec.cElementType) return this.getDefaultValue(spec);
if (returnType === spec.cTypeName) return 'nullptr';
return '0';
}
private convertFromCpp(spec: ArraySpecialization, expr: string): string {
if (spec.isPointer) {
return `(${spec.cElementType}) ${expr}`;
}
if (spec.isEnum && spec.elementType !== 'PropertyId') {
return `(${spec.cElementType}) ${expr}`;
}
return expr;
}
private convertToCpp(spec: ArraySpecialization, expr: string): string {
if (spec.isPointer) {
return `(${spec.elementType}) ${expr}`;
}
if (spec.isEnum && spec.elementType !== 'PropertyId') {
return `(${spec.elementType}) ${expr}`;
}
return expr;
}
}

View File

@ -1,120 +0,0 @@
import { Type, Member, toSnakeCase, toCFunctionName, toCTypeName, Constructor, ClassOrStruct } from '../types';
export interface GeneratorResult {
declarations: string[];
implementations: string[];
}
export class ConstructorGenerator {
constructor(private validTypes: Set<string>) {}
generate(type: ClassOrStruct): GeneratorResult {
const declarations: string[] = [];
const implementations: string[] = [];
if (!type.members) return { declarations, implementations };
const constructors = type.members.filter(m => m.kind === 'constructor');
const cTypeName = `spine_${toSnakeCase(type.name)}`;
// Skip constructor generation for abstract types
if (!type.isAbstract) {
// Generate create functions for each constructor
let constructorIndex = 0;
for (const constructor of constructors) {
const funcName = this.getCreateFunctionName(type.name, constructor, constructorIndex);
const params = this.generateParameters(constructor);
// Declaration
declarations.push(`SPINE_C_EXPORT ${cTypeName} ${funcName}(${params.declaration});`);
// Implementation
implementations.push(`${cTypeName} ${funcName}(${params.declaration}) {`);
implementations.push(` ${type.name} *obj = new (__FILE__, __LINE__) ${type.name}(${params.call});`);
implementations.push(` return (${cTypeName}) obj;`);
implementations.push(`}`);
implementations.push('');
constructorIndex++;
}
}
// Always generate dispose function
declarations.push(`SPINE_C_EXPORT void ${cTypeName}_dispose(${cTypeName} obj);`);
implementations.push(`void ${cTypeName}_dispose(${cTypeName} obj) {`);
implementations.push(` if (!obj) return;`);
implementations.push(` delete (${type.name} *) obj;`);
implementations.push(`}`);
implementations.push('');
return { declarations, implementations };
}
private getCreateFunctionName(typeName: string, constructor: Constructor, index: number): string {
const baseName = `spine_${toSnakeCase(typeName)}_create`;
if (!constructor.parameters || constructor.parameters.length === 0) {
return baseName;
}
if (index === 0) {
return baseName;
}
// Generate name based on parameter types
const paramNames = constructor.parameters
.map(p => this.getParamTypeSuffix(p.type))
.join('_');
return `${baseName}_with_${paramNames}`;
}
private getParamTypeSuffix(type: string): string {
if (type.includes('float')) return 'float';
if (type.includes('int')) return 'int';
if (type.includes('bool')) return 'bool';
if (type.includes('String') || type.includes('char')) return 'string';
// Extract class name from pointers/references
const match = type.match(/(?:const\s+)?(\w+)(?:\s*[*&])?/);
if (match) {
return toSnakeCase(match[1]);
}
return 'param';
}
private generateParameters(constructor: Constructor): { declaration: string; call: string } {
if (!constructor.parameters || constructor.parameters.length === 0) {
return { declaration: 'void', call: '' };
}
const declParts: string[] = [];
const callParts: string[] = [];
for (const param of constructor.parameters) {
const cType = toCTypeName(param.type, this.validTypes);
declParts.push(`${cType} ${param.name}`);
// Convert C type back to C++ for the call
let callExpr = param.name;
if (param.type === 'const String &' || param.type === 'String') {
callExpr = `String(${param.name})`;
} else if (param.type.includes('*')) {
callExpr = `(${param.type}) ${param.name}`;
} else if (param.type.includes('&')) {
// Handle reference types - need to dereference the pointer
const baseType = param.type.replace(/^(?:const\s+)?(.+?)\s*&$/, '$1').trim();
callExpr = `*(${baseType}*) ${param.name}`;
}
callParts.push(callExpr);
}
return {
declaration: declParts.join(', '),
call: callParts.join(', ')
};
}
}

View File

@ -1,36 +0,0 @@
import { Enum, toSnakeCase } from '../types';
export class EnumGenerator {
generate(enumType: Enum): string[] {
const lines: string[] = [];
const enumName = `spine_${toSnakeCase(enumType.name)}`;
lines.push(`typedef enum ${enumName} {`);
if (enumType.values) {
for (let i = 0; i < enumType.values.length; i++) {
const value = enumType.values[i];
// Remove redundant enum name prefix from value name if present
let valueName = value.name;
if (valueName.toLowerCase().startsWith(enumType.name.toLowerCase())) {
valueName = valueName.substring(enumType.name.length);
// Remove leading underscore if present after removing prefix
if (valueName.startsWith('_')) {
valueName = valueName.substring(1);
}
}
const cName = `SPINE_${toSnakeCase(enumType.name).toUpperCase()}_${toSnakeCase(valueName).toUpperCase()}`;
if (value.value !== undefined) {
lines.push(` ${cName} = ${value.value}${i < enumType.values.length - 1 ? ',' : ''}`);
} else {
lines.push(` ${cName}${i < enumType.values.length - 1 ? ',' : ''}`);
}
}
}
lines.push(`} ${enumName};`);
return lines;
}
}

View File

@ -1,378 +0,0 @@
import { Type, Member, toSnakeCase, toCFunctionName, toCTypeName, Exclusion, Method, ClassOrStruct } from '../types';
import { isMethodExcluded } from '../exclusions';
import { GeneratorResult } from './constructor-generator';
export class MethodGenerator {
constructor(private exclusions: Exclusion[], private validTypes: Set<string>) { }
generate(type: ClassOrStruct): GeneratorResult {
const declarations: string[] = [];
const implementations: string[] = [];
if (!type.members) return { declarations, implementations };
const methods = type.members.filter(m =>
m.kind === 'method'
).filter(m => !isMethodExcluded(type.name, m.name, this.exclusions, m))
.filter(m => !m.isStatic);
// Check for const/non-const method pairs
const methodGroups = new Map<string, Method[]>();
for (const method of methods) {
const key = method.name + '(' + (method.parameters?.map(p => p.type).join(',') || '') + ')';
if (!methodGroups.has(key)) {
methodGroups.set(key, []);
}
methodGroups.get(key)!.push(method);
}
// Collect all errors before failing
const errors: string[] = [];
// Report errors for duplicate methods with different signatures
for (const [signature, group] of methodGroups) {
if (group.length > 1) {
// Check if we have different return types (const vs non-const overloading)
const returnTypes = new Set(group.map(m => m.returnType));
if (returnTypes.size > 1) {
let error = `\nERROR: Type '${type.name}' has multiple versions of method '${group[0].name}' with different return types:\n`;
error += `This is likely a const/non-const overload pattern in C++ which cannot be represented in C.\n\n`;
for (const method of group) {
const source = method.fromSupertype ? ` (inherited from ${method.fromSupertype})` : '';
error += ` - ${method.returnType || 'void'} ${method.name}()${source}\n`;
}
error += `\nC++ pattern detected:\n`;
error += ` T& ${group[0].name}() { return member; } // for non-const objects\n`;
error += ` const T& ${group[0].name}() const { return member; } // for const objects\n`;
error += `\nThis pattern provides const-correctness in C++ but cannot be represented in C.\n`;
error += `Consider:\n`;
error += ` 1. Only exposing the const version in the C API\n`;
error += ` 2. Renaming one of the methods in C++\n`;
error += ` 3. Adding the method to exclusions.txt\n`;
errors.push(error);
}
}
}
// If we have errors, throw them all at once
if (errors.length > 0) {
console.error("=".repeat(80));
console.error("CONST/NON-CONST METHOD CONFLICTS FOUND");
console.error("=".repeat(80));
for (const error of errors) {
console.error(error);
}
console.error("=".repeat(80));
console.error(`Total conflicts found: ${errors.length}`);
console.error("=".repeat(80));
throw new Error(`Cannot generate C API due to ${errors.length} const/non-const overloaded method conflicts.`);
}
// For now, continue with the unique methods
const uniqueMethods = methods;
const cTypeName = `spine_${toSnakeCase(type.name)}`;
// Track method names to detect overloads
const methodCounts = new Map<string, number>();
for (const method of uniqueMethods) {
const count = methodCounts.get(method.name) || 0;
methodCounts.set(method.name, count + 1);
}
// Track how many times we've seen each method name
const methodSeenCounts = new Map<string, number>();
for (const method of uniqueMethods) {
try {
// Handle getters
if (method.name.startsWith('get') && (!method.parameters || method.parameters.length === 0)) {
const propName = method.name.substring(3);
// Check if return type is Array
if (method.returnType && method.returnType.includes('Array<')) {
// For Array types, only generate collection accessors
this.generateCollectionAccessors(type, method, propName, declarations, implementations);
} else if (method.name === 'getRTTI') {
// Special handling for getRTTI - make it static
const funcName = toCFunctionName(type.name, 'get_rtti');
const returnType = 'spine_rtti';
declarations.push(`SPINE_C_EXPORT ${returnType} ${funcName}();`);
implementations.push(`${returnType} ${funcName}() {`);
implementations.push(` return (spine_rtti) &${type.name}::rtti;`);
implementations.push(`}`);
implementations.push('');
} else {
// For non-Array types, generate regular getter
const funcName = toCFunctionName(type.name, method.name);
const returnType = toCTypeName(method.returnType || 'void', this.validTypes);
declarations.push(`SPINE_C_EXPORT ${returnType} ${funcName}(${cTypeName} obj);`);
implementations.push(`${returnType} ${funcName}(${cTypeName} obj) {`);
implementations.push(` if (!obj) return ${this.getDefaultReturn(returnType)};`);
implementations.push(` ${type.name} *_obj = (${type.name} *) obj;`);
const callExpr = this.generateMethodCall('_obj', method);
implementations.push(` return ${callExpr};`);
implementations.push(`}`);
implementations.push('');
}
}
// Handle setters
else if (method.name.startsWith('set') && method.parameters && method.parameters.length === 1) {
const funcName = toCFunctionName(type.name, method.name);
const paramType = toCTypeName(method.parameters[0].type, this.validTypes);
declarations.push(`SPINE_C_EXPORT void ${funcName}(${cTypeName} obj, ${paramType} value);`);
implementations.push(`void ${funcName}(${cTypeName} obj, ${paramType} value) {`);
implementations.push(` if (!obj) return;`);
implementations.push(` ${type.name} *_obj = (${type.name} *) obj;`);
const callExpr = this.generateSetterCall('_obj', method, 'value');
implementations.push(` ${callExpr};`);
implementations.push(`}`);
implementations.push('');
}
// Handle other methods
else {
// Check if this is an overloaded method
const isOverloaded = (methodCounts.get(method.name) || 0) > 1;
const seenCount = methodSeenCounts.get(method.name) || 0;
methodSeenCounts.set(method.name, seenCount + 1);
// Generate function name with suffix for overloads
let funcName = toCFunctionName(type.name, method.name);
// Check for naming conflicts with type names
if (method.name === 'pose' && type.name === 'Bone') {
// Rename bone_pose() method to avoid conflict with spine_bone_pose type
funcName = toCFunctionName(type.name, 'update_pose');
}
if (isOverloaded && seenCount > 0) {
// Add parameter count suffix for overloaded methods
const paramCount = method.parameters ? method.parameters.length : 0;
funcName = `${funcName}_${paramCount}`;
}
const returnType = toCTypeName(method.returnType || 'void', this.validTypes);
const params = this.generateMethodParameters(cTypeName, method);
declarations.push(`SPINE_C_EXPORT ${returnType} ${funcName}(${params.declaration});`);
implementations.push(`${returnType} ${funcName}(${params.declaration}) {`);
implementations.push(` if (!obj) return ${this.getDefaultReturn(returnType)};`);
implementations.push(` ${type.name} *_obj = (${type.name} *) obj;`);
const callExpr = this.generateMethodCall('_obj', method, params.call);
if (returnType === 'void') {
implementations.push(` ${callExpr};`);
} else {
implementations.push(` return ${callExpr};`);
}
implementations.push(`}`);
implementations.push('');
}
} catch (error) {
console.error(`Error generating method for type ${type.name}::${method.name}:`);
console.error(error);
throw error;
}
}
return { declarations, implementations };
}
private generateCollectionAccessors(type: Type, method: Method, propName: string,
declarations: string[], implementations: string[]) {
const cTypeName = `spine_${toSnakeCase(type.name)}`;
const propSnake = toSnakeCase(propName);
const arrayMatch = method.returnType!.match(/Array<(.+?)>/);
if (!arrayMatch) return;
const elementType = arrayMatch[1].trim().replace(/\s*\*$/, '');
let cElementType: string;
if (elementType === 'int') {
cElementType = 'int';
} else if (elementType === 'float') {
cElementType = 'float';
} else if (elementType === 'uint8_t') {
cElementType = 'uint8_t';
} else if (elementType === 'String') {
cElementType = 'const char *';
} else if (elementType === 'PropertyId') {
cElementType = 'int64_t'; // PropertyId is just an int
} else {
cElementType = `spine_${toSnakeCase(elementType)}`;
}
// Get count function
const getCountFunc = `spine_${toSnakeCase(type.name)}_get_num_${propSnake}`;
declarations.push(`SPINE_C_EXPORT int32_t ${getCountFunc}(${cTypeName} obj);`);
implementations.push(`int32_t ${getCountFunc}(${cTypeName} obj) {`);
implementations.push(` if (!obj) return 0;`);
implementations.push(` ${type.name} *_obj = (${type.name} *) obj;`);
implementations.push(` return (int32_t) _obj->get${propName}().size();`);
implementations.push(`}`);
implementations.push('');
// Get array function
const getArrayFunc = `spine_${toSnakeCase(type.name)}_get_${propSnake}`;
declarations.push(`SPINE_C_EXPORT ${cElementType} *${getArrayFunc}(${cTypeName} obj);`);
implementations.push(`${cElementType} *${getArrayFunc}(${cTypeName} obj) {`);
implementations.push(` if (!obj) return nullptr;`);
implementations.push(` ${type.name} *_obj = (${type.name} *) obj;`);
// Handle const vs non-const arrays
if (method.isConst || method.returnType!.includes('const')) {
// For const arrays, we need to copy the data or use data() method
implementations.push(` auto& vec = _obj->get${propName}();`);
implementations.push(` if (vec.size() == 0) return nullptr;`);
implementations.push(` return (${cElementType} *) &vec[0];`);
} else {
implementations.push(` return (${cElementType} *) _obj->get${propName}().buffer();`);
}
implementations.push(`}`);
implementations.push('');
}
private generateMethodCall(objName: string, method: Method, args?: string): string {
let call = `${objName}->${method.name}(${args || ''})`;
// Handle return type conversions
if (method.returnType) {
if (method.returnType === 'const String &' || method.returnType === 'String') {
call = `(const char *) ${call}.buffer()`;
} else if (method.returnType === 'const RTTI &' || method.returnType === 'RTTI &') {
// RTTI needs special handling - return as opaque pointer
call = `(spine_rtti) &${call}`;
} else if (method.returnType.includes('*')) {
const cType = toCTypeName(method.returnType, this.validTypes);
call = `(${cType}) ${call}`;
} else if (method.returnType === 'Color &' || method.returnType === 'const Color &') {
call = `(spine_color) &${call}`;
} else if (method.returnType.includes('&')) {
// For reference returns, take the address
const cType = toCTypeName(method.returnType, this.validTypes);
call = `(${cType}) &${call}`;
} else if (!this.isPrimitiveType(method.returnType) && !this.isEnumType(method.returnType)) {
// For non-primitive value returns (e.g., BoneLocal), take the address
const cType = toCTypeName(method.returnType, this.validTypes);
call = `(${cType}) &${call}`;
} else if (this.isEnumType(method.returnType)) {
// Cast enum return values
const cType = toCTypeName(method.returnType, this.validTypes);
call = `(${cType}) ${call}`;
}
}
return call;
}
private generateSetterCall(objName: string, method: Method, valueName: string): string {
const param = method.parameters![0];
let value = valueName;
// Convert from C type to C++
if (param.type === 'const String &' || param.type === 'String') {
value = `String(${valueName})`;
} else if (param.type.includes('Array<')) {
// Array types are passed as void* and need to be cast back
value = `(${param.type}) ${valueName}`;
} else if (param.type.includes('*')) {
value = `(${param.type}) ${valueName}`;
} else if (param.type.includes('&') && !param.type.includes('const')) {
// Non-const reference parameters need to dereference the C pointer
const baseType = param.type.replace(/\s*&\s*$/, '').trim();
value = `*((${baseType}*) ${valueName})`;
} else if (this.isEnumType(param.type)) {
// Cast enum types
value = `(${param.type}) ${valueName}`;
}
return `${objName}->${method.name}(${value})`;
}
private generateMethodParameters(objTypeName: string, method: Method): { declaration: string; call: string } {
const declParts = [`${objTypeName} obj`];
const callParts: string[] = [];
if (method.parameters) {
for (const param of method.parameters) {
const cType = toCTypeName(param.type, this.validTypes);
declParts.push(`${cType} ${param.name}`);
// Convert C type back to C++ for the call
let callExpr = param.name;
if (param.type === 'const String &' || param.type === 'String') {
callExpr = `String(${param.name})`;
} else if (param.type.includes('Array<')) {
// Array types are passed as void* and need to be cast back
callExpr = `(${param.type}) ${param.name}`;
} else if (param.type.includes('*')) {
callExpr = `(${param.type}) ${param.name}`;
} else if (param.type.includes('&')) {
// Handle reference types
const isConst = param.type.startsWith('const');
const baseType = param.type.replace(/^(?:const\s+)?(.+?)\s*&$/, '$1').trim();
// Non-const references to primitive types are output parameters - just pass the pointer
if (!isConst && ['float', 'int', 'double', 'bool'].includes(baseType)) {
callExpr = `*${param.name}`;
} else {
// Const references and non-primitive types need dereferencing
callExpr = `*(${baseType}*) ${param.name}`;
}
} else if (this.isEnumType(param.type)) {
// Cast enum types
callExpr = `(${param.type}) ${param.name}`;
}
callParts.push(callExpr);
}
}
return {
declaration: declParts.join(', '),
call: callParts.join(', ')
};
}
private getDefaultReturn(returnType: string): string {
if (returnType === 'void') return '';
if (returnType === 'bool') return 'false';
if (returnType.includes('int') || returnType === 'float' || returnType === 'double' || returnType === 'size_t') return '0';
// Check if it's an enum type (spine_* types that are not pointers)
if (returnType.startsWith('spine_') && !returnType.includes('*')) {
// Cast 0 to the enum type
return `(${returnType}) 0`;
}
return 'nullptr';
}
private isEnumType(type: string): boolean {
// List of known enum types in spine-cpp
const enumTypes = [
'EventType', 'Format', 'TextureFilter', 'TextureWrap',
'AttachmentType', 'BlendMode', 'Inherit', 'MixBlend',
'MixDirection', 'Physics', 'PositionMode', 'Property',
'RotateMode', 'SequenceMode', 'SpacingMode'
];
return enumTypes.includes(type);
}
private isPrimitiveType(type: string): boolean {
return ['int', 'float', 'double', 'bool', 'size_t', 'int32_t', 'uint32_t',
'int16_t', 'uint16_t', 'uint8_t', 'void'].includes(type);
}
}

View File

@ -96,8 +96,8 @@ namespace spine {
TextureWrap vWrap;
int width, height;
bool pma;
int index;
void *texture;
int index;
void *texture;
explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888),
minFilter(TextureFilter_Nearest),
@ -108,13 +108,16 @@ namespace spine {
class SP_API AtlasRegion : public TextureRegion {
public:
AtlasRegion() : TextureRegion(), page(nullptr), name(""), index(0), x(0), y(0) {}
~AtlasRegion() {}
AtlasPage *page;
String name;
int index;
int x, y;
Array<int> splits;
Array<int> pads;
Array <String> names;
Array<String> names;
Array<float> values;
};
@ -146,6 +149,6 @@ namespace spine {
void load(const char *begin, int length, const char *dir, bool createTexture);
};
}
}// namespace spine
#endif /* Spine_Atlas_h */

View File

@ -121,7 +121,7 @@ namespace spine {
color.a = len >= 8 ? parseHex(hexString, 3) : 1.0f;
}
}
static float parseHex(const char* value, size_t index) {
char digits[3];
digits[0] = value[index * 2];

View File

@ -41,6 +41,9 @@ namespace spine {
class BonePose;
class TransformConstraintPose;
class FromProperty;
class ToProperty;
/// Source property for a TransformConstraint.
class SP_API FromProperty : public SpineObject {
friend class SkeletonBinary;
@ -50,7 +53,7 @@ namespace spine {
float _offset;
/// Constrained properties.
Array<class ToProperty*> _to;
Array<ToProperty*> _to;
FromProperty();
virtual ~FromProperty();
@ -85,66 +88,102 @@ namespace spine {
class SP_API FromRotate : public FromProperty {
public:
FromRotate() : FromProperty() {}
~FromRotate() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToRotate : public ToProperty {
public:
ToRotate() : ToProperty() {}
~ToRotate() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
class SP_API FromX : public FromProperty {
public:
FromX() : FromProperty() {}
~FromX() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToX : public ToProperty {
public:
ToX() : ToProperty() {}
~ToX() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
class SP_API FromY : public FromProperty {
public:
FromY() : FromProperty() {}
~FromY() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToY : public ToProperty {
public:
ToY() : ToProperty() {}
~ToY() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
class SP_API FromScaleX : public FromProperty {
public:
FromScaleX() : FromProperty() {}
~FromScaleX() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToScaleX : public ToProperty {
public:
ToScaleX() : ToProperty() {}
~ToScaleX() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
class SP_API FromScaleY : public FromProperty {
public:
FromScaleY() : FromProperty() {}
~FromScaleY() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToScaleY : public ToProperty {
public:
ToScaleY() : ToProperty() {}
~ToScaleY() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
class SP_API FromShearY : public FromProperty {
public:
FromShearY() : FromProperty() {}
~FromShearY() {}
float value(Skeleton &skeleton, BonePose& source, bool local, float* offsets) override;
};
class SP_API ToShearY : public ToProperty {
public:
ToShearY() : ToProperty() {}
~ToShearY() {}
float mix(TransformConstraintPose& pose) override;
void apply(Skeleton &skeleton, TransformConstraintPose& pose, BonePose& bone, float value, bool local, bool additive) override;
};
@ -216,14 +255,14 @@ namespace spine {
void setClamp(bool clamp);
/// The mapping of transform properties to other transform properties.
Array<class FromProperty*>& getProperties();
Array<FromProperty*>& getProperties();
private:
Array<BoneData*> _bones;
BoneData* _source;
float _offsets[6]; // [rotation, x, y, scaleX, scaleY, shearY]
bool _localSource, _localTarget, _additive, _clamp;
Array<class FromProperty*> _properties;
Array<FromProperty*> _properties;
};
}

View File

@ -1,43 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#ifndef Spine_Vertices_h
#define Spine_Vertices_h
#include <spine/Array.h>
namespace spine {
class SP_API Vertices : public SpineObject {
public:
Array <int> _bones;
Array<float> _weights;
};
}
#endif /* Spine_Vertices_h */

View File

@ -73,7 +73,6 @@
#include <spine/TransformConstraintData.h>
#include <spine/TransformConstraintTimeline.h>
#include <spine/TranslateTimeline.h>
#include <spine/Vertices.h>
#include <spine/SequenceTimeline.h>
#include <spine/Version.h>
#include <spine/SliderData.h>
@ -85,29 +84,42 @@
using namespace spine;
class SP_API Vertices : public SpineObject {
public:
Array<int> _bones;
Array<float> _weights;
};
#define SKELETON_JSON_ERROR(root, message, value) \
do { \
delete skeletonData; \
setError(root, message, value); \
return NULL; \
do { \
delete skeletonData; \
setError(root, message, value); \
return NULL; \
} while (0)
static FromProperty* fromProperty(const char* type) {
static FromProperty *fromProperty(const char *type) {
if (strcmp(type, "rotate") == 0) return new FromRotate();
else if (strcmp(type, "x") == 0) return new FromX();
else if (strcmp(type, "y") == 0) return new FromY();
else if (strcmp(type, "scaleX") == 0) return new FromScaleX();
else if (strcmp(type, "scaleY") == 0) return new FromScaleY();
else if (strcmp(type, "shearY") == 0) return new FromShearY();
else return NULL;
else if (strcmp(type, "x") == 0)
return new FromX();
else if (strcmp(type, "y") == 0)
return new FromY();
else if (strcmp(type, "scaleX") == 0)
return new FromScaleX();
else if (strcmp(type, "scaleY") == 0)
return new FromScaleY();
else if (strcmp(type, "shearY") == 0)
return new FromShearY();
else
return NULL;
}
static float propertyScale(const char* type, float scale) {
static float propertyScale(const char *type, float scale) {
if (strcmp(type, "x") == 0 || strcmp(type, "y") == 0) return scale;
else return 1;
else
return 1;
}
SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new (__FILE__, __LINE__) AtlasAttachmentLoader(atlas)),
SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new(__FILE__, __LINE__) AtlasAttachmentLoader(atlas)),
_scale(1), _ownsLoader(true) {}
SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader),
@ -284,8 +296,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
skeletonData->_ikConstraints.add(data);
skeletonData->_constraints.add(data);
}
else if (strcmp(type, "transform") == 0) {
} else if (strcmp(type, "transform") == 0) {
TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(name);
data->setSkinRequired(skinRequired);
@ -351,7 +362,8 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
}
if (from->_to.size() > 0) data->_properties.add(from);
else delete from;
else
delete from;
}
}
@ -372,8 +384,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
skeletonData->_transformConstraints.add(data);
skeletonData->_constraints.add(data);
}
else if (strcmp(type, "path") == 0) {
} else if (strcmp(type, "path") == 0) {
PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(name);
data->setSkinRequired(skinRequired);
@ -404,8 +415,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
skeletonData->_pathConstraints.add(data);
skeletonData->_constraints.add(data);
}
else if (strcmp(type, "physics") == 0) {
} else if (strcmp(type, "physics") == 0) {
PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData(name);
data->setSkinRequired(skinRequired);
@ -438,8 +448,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
skeletonData->_physicsConstraints.add(data);
skeletonData->_constraints.add(data);
}
else if (strcmp(type, "slider") == 0) {
} else if (strcmp(type, "slider") == 0) {
SliderData *data = new (__FILE__, __LINE__) SliderData(name);
data->setSkinRequired(skinRequired);
data->_additive = Json::getBoolean(constraintMap, "additive", false);
@ -529,7 +538,8 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
for (Json *entry = slotEntry->_child; entry; entry = entry->_next) {
Attachment *attachment = readAttachment(entry, skin, slot->getIndex(), entry->_name, skeletonData);
if (attachment) skin->setAttachment(slot->getIndex(), entry->_name, attachment);
else SKELETON_JSON_ERROR(root, "Error reading attachment: ", entry->_name);
else
SKELETON_JSON_ERROR(root, "Error reading attachment: ", entry->_name);
}
}
@ -550,7 +560,7 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent);
if (parent == NULL) SKELETON_JSON_ERROR(root, "Parent mesh not found: ", linkedMesh->_parent.buffer());
linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimelines ? static_cast<VertexAttachment *>(parent)
: linkedMesh->_mesh;
: linkedMesh->_mesh;
linkedMesh->_mesh->setParentMesh(static_cast<MeshAttachment *>(parent));
if (linkedMesh->_mesh->_region != NULL) linkedMesh->_mesh->updateRegion();
}
@ -694,7 +704,7 @@ Attachment *SkeletonJson::readAttachment(Json *map, Skin *skin, int slotIndex, c
readVertices(map, path, vertexCount << 1);
if (!Json::asArray(Json::getItem(map, "lengths"), path->_lengths)) return NULL;
for (int i = 0; i < (int)path->_lengths.size(); i++)
for (int i = 0; i < (int) path->_lengths.size(); i++)
path->_lengths[i] *= scale;
const char *color = Json::getString(map, "color", NULL);
@ -749,7 +759,7 @@ void SkeletonJson::readVertices(Json *map, VertexAttachment *attachment, size_t
if (!Json::asArray(Json::getItem(map, "vertices"), vertices)) return;
if (verticesLength == vertices.size()) {
if (_scale != 1) {
for (int i = 0; i < (int)vertices.size(); ++i)
for (int i = 0; i < (int) vertices.size(); ++i)
vertices[i] *= _scale;
}
attachment->getVertices().clearAndAddAll(vertices);
@ -824,7 +834,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
} else if (strcmp(timelineMap->_name, "rgb") == 0) {
RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frames, frames * 3, slotIndex);
float time = Json::getFloat(keyMap, "time", 0);
const char* colorStr = Json::getString(keyMap, "color", 0);
const char *colorStr = Json::getString(keyMap, "color", 0);
Color color;
if (colorStr && strlen(colorStr) >= 6) {
color.r = Color::parseHex(colorStr, 0);
@ -839,7 +849,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
break;
}
float time2 = Json::getFloat(nextMap, "time", 0);
const char* colorStr2 = Json::getString(nextMap, "color", 0);
const char *colorStr2 = Json::getString(nextMap, "color", 0);
Color newColor;
if (colorStr2 && strlen(colorStr2) >= 6) {
newColor.r = Color::parseHex(colorStr2, 0);
@ -866,7 +876,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
float time = Json::getFloat(keyMap, "time", 0);
Color color, color2;
Color::valueOf(Json::getString(keyMap, "light", 0), color);
const char* darkStr = Json::getString(keyMap, "dark", 0);
const char *darkStr = Json::getString(keyMap, "dark", 0);
if (darkStr && strlen(darkStr) >= 6) {
color2.r = Color::parseHex(darkStr, 0);
color2.g = Color::parseHex(darkStr, 1);
@ -882,7 +892,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
float time2 = Json::getFloat(nextMap, "time", 0);
Color newColor, newColor2;
Color::valueOf(Json::getString(nextMap, "light", 0), newColor);
const char* darkStr2 = Json::getString(nextMap, "dark", 0);
const char *darkStr2 = Json::getString(nextMap, "dark", 0);
if (darkStr2 && strlen(darkStr2) >= 6) {
newColor2.r = Color::parseHex(darkStr2, 0);
newColor2.g = Color::parseHex(darkStr2, 1);
@ -907,14 +917,14 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
} else if (strcmp(timelineMap->_name, "rgb2") == 0) {
RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frames, frames * 6, slotIndex);
float time = Json::getFloat(keyMap, "time", 0);
const char* lightStr = Json::getString(keyMap, "light", 0);
const char *lightStr = Json::getString(keyMap, "light", 0);
Color color, color2;
if (lightStr && strlen(lightStr) >= 6) {
color.r = Color::parseHex(lightStr, 0);
color.g = Color::parseHex(lightStr, 1);
color.b = Color::parseHex(lightStr, 2);
}
const char* darkStr = Json::getString(keyMap, "dark", 0);
const char *darkStr = Json::getString(keyMap, "dark", 0);
if (darkStr && strlen(darkStr) >= 6) {
color2.r = Color::parseHex(darkStr, 0);
color2.g = Color::parseHex(darkStr, 1);
@ -928,14 +938,14 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
break;
}
float time2 = Json::getFloat(nextMap, "time", 0);
const char* lightStr2 = Json::getString(nextMap, "light", 0);
const char *lightStr2 = Json::getString(nextMap, "light", 0);
Color newColor, newColor2;
if (lightStr2 && strlen(lightStr2) >= 6) {
newColor.r = Color::parseHex(lightStr2, 0);
newColor.g = Color::parseHex(lightStr2, 1);
newColor.b = Color::parseHex(lightStr2, 2);
}
const char* darkStr2 = Json::getString(nextMap, "dark", 0);
const char *darkStr2 = Json::getString(nextMap, "dark", 0);
if (darkStr2 && strlen(darkStr2) >= 6) {
newColor2.r = Color::parseHex(darkStr2, 0);
newColor2.g = Color::parseHex(darkStr2, 1);
@ -1029,7 +1039,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
float mix = Json::getFloat(keyMap, "mix", 1), softness = Json::getFloat(keyMap, "softness", 0) * _scale;
for (int frame = 0, bezier = 0;; frame++) {
timeline->setFrame(frame, time, mix, softness, Json::getBoolean(keyMap, "bendPositive", true) ? 1 : -1,
Json::getBoolean(keyMap, "compress", false), Json::getBoolean(keyMap, "stretch", false));
Json::getBoolean(keyMap, "compress", false), Json::getBoolean(keyMap, "stretch", false));
Json *nextMap = keyMap->_next;
if (!nextMap) {
break;
@ -1123,15 +1133,15 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
readTimeline(timelines, keyMap, timeline, 0, constraint->_positionMode == PositionMode_Fixed ? _scale : 1);
} else if (strcmp(timelineName, "spacing") == 0) {
CurveTimeline1 *timeline = new (__FILE__, __LINE__) PathConstraintSpacingTimeline(frames, frames,
index);
index);
readTimeline(timelines, keyMap, timeline, 0,
constraint->_spacingMode == SpacingMode_Length ||
constraint->_spacingMode == SpacingMode_Fixed
? _scale
: 1);
constraint->_spacingMode == SpacingMode_Fixed
? _scale
: 1);
} else if (strcmp(timelineName, "mix") == 0) {
PathConstraintMixTimeline *timeline = new (__FILE__, __LINE__) PathConstraintMixTimeline(frames,
frames * 3, index);
frames * 3, index);
float time = Json::getFloat(keyMap, "time", 0);
float mixRotate = Json::getFloat(keyMap, "mixRotate", 1);
float mixX = Json::getFloat(keyMap, "mixX", 1);
@ -1307,7 +1317,7 @@ Animation *SkeletonJson::readAnimation(Json *map, SkeletonData *skeletonData) {
for (int frame = 0; keyMap != NULL; keyMap = keyMap->_next, frame++) {
float delay = Json::getFloat(keyMap, "delay", lastDelay);
timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0),
SequenceMode_valueOf(Json::getString(keyMap, "mode", "hold")), Json::getInt(keyMap, "index", 0), delay);
SequenceMode_valueOf(Json::getString(keyMap, "mode", "hold")), Json::getInt(keyMap, "index", 0), delay);
lastDelay = delay;
}
timelines.add(timeline);
@ -1408,7 +1418,7 @@ void SkeletonJson::readTimeline(Array<Timeline *> &timelines, Json *keyMap, Curv
}
void SkeletonJson::readTimeline(Array<Timeline *> &timelines, Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2,
float defaultValue, float scale) {
float defaultValue, float scale) {
float time = Json::getFloat(keyMap, "time", 0);
float value1 = Json::getFloat(keyMap, name1, defaultValue) * scale, value2 = Json::getFloat(keyMap, name2, defaultValue) * scale;
for (int frame = 0, bezier = 0;; frame++) {