mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
[cpp] Added type extractor script to be used by C wrapper generator.
This commit is contained in:
parent
b69e2ca708
commit
46410f4b8a
4
.gitignore
vendored
4
.gitignore
vendored
@ -236,3 +236,7 @@ spine-libgdx/spine-skeletonviewer/.settings/org.eclipse.jdt.apt.core.prefs
|
||||
spine-cpp/.cache
|
||||
.build
|
||||
spine-libgdx/.settings
|
||||
docs/
|
||||
out.json
|
||||
spine-cpp/extraction.log
|
||||
extraction.log
|
||||
|
||||
654
spine-cpp/extract-spine-cpp-types.js
Executable file
654
spine-cpp/extract-spine-cpp-types.js
Executable file
@ -0,0 +1,654 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const scriptDir = path.dirname(path.resolve(__filename));
|
||||
const spineIncludeDir = path.join(scriptDir, 'spine-cpp', 'include');
|
||||
|
||||
function showHelp() {
|
||||
console.log(`
|
||||
extract-spine-cpp-types.js - Extract type information from spine-cpp header files
|
||||
|
||||
USAGE:
|
||||
extract-spine-cpp-types.js [<header-file>]
|
||||
extract-spine-cpp-types.js --help
|
||||
|
||||
DESCRIPTION:
|
||||
Extracts classes, structs, enums, and their public members from C++ headers.
|
||||
Without arguments, processes all headers in spine-cpp/include.
|
||||
|
||||
In all-files mode, performs two passes:
|
||||
1. Extract all type definitions
|
||||
2. Add inherited methods from supertypes (including template instantiations)
|
||||
|
||||
OUTPUT FORMAT:
|
||||
Single file mode: Array of type definitions
|
||||
All files mode: Object with relative file paths as keys
|
||||
|
||||
TYPE DEFINITION STRUCTURE:
|
||||
{
|
||||
"name": "ClassName", // Type name
|
||||
"kind": "class" | "struct" | "enum", // Type kind
|
||||
"loc": { // Source location
|
||||
"line": 45,
|
||||
"col": 15
|
||||
},
|
||||
"superTypes": ["BaseClass", "Interface"], // Base classes (optional)
|
||||
"members": [...], // Public members (classes/structs)
|
||||
"values": [...], // Enum constants (enums only)
|
||||
"isTemplate": true, // Present if type is a template
|
||||
"templateParams": ["T", "U"], // Template parameter names (templates only)
|
||||
"isAbstract": true // Present if class has pure virtual methods
|
||||
}
|
||||
|
||||
MEMBER STRUCTURE:
|
||||
Fields:
|
||||
{
|
||||
"kind": "field",
|
||||
"name": "fieldName",
|
||||
"type": "int"
|
||||
}
|
||||
|
||||
Methods:
|
||||
{
|
||||
"kind": "method",
|
||||
"name": "methodName",
|
||||
"returnType": "void",
|
||||
"parameters": [
|
||||
{"name": "param1", "type": "int"},
|
||||
{"name": "param2", "type": "const String &"}
|
||||
],
|
||||
"isStatic": false,
|
||||
"isVirtual": true,
|
||||
"isPure": false,
|
||||
"fromSupertype": "BaseClass" // Present if inherited (all-files mode only)
|
||||
}
|
||||
|
||||
Constructors:
|
||||
{
|
||||
"kind": "constructor",
|
||||
"name": "ClassName",
|
||||
"parameters": [...]
|
||||
}
|
||||
|
||||
ENUM VALUES:
|
||||
{
|
||||
"name": "ENUM_VALUE",
|
||||
"value": "1 << 0" // Present only if explicitly initialized
|
||||
}
|
||||
|
||||
INHERITANCE (all-files mode only):
|
||||
- Methods inherited from non-template supertypes are included
|
||||
- Methods inherited from template supertypes have parameters substituted
|
||||
- SpineObject methods are excluded (considered noise)
|
||||
- Methods are marked with "fromSupertype" field
|
||||
- Template supertypes appear as "TemplateName<Type1, Type2>"
|
||||
|
||||
NOTES:
|
||||
- Only public members are extracted
|
||||
- Forward declarations are excluded
|
||||
- Template instantiations (e.g., Vector<int>) are excluded
|
||||
- Friend declarations are excluded
|
||||
- Implicit/compiler-generated methods are excluded
|
||||
- Destructors and operator methods are excluded (not needed for C wrapper generation)
|
||||
|
||||
EXAMPLES:
|
||||
# Extract types from single file
|
||||
extract-spine-cpp-types.js spine-cpp/include/spine/Animation.h > animation.json
|
||||
|
||||
# Extract all types with inheritance resolution
|
||||
extract-spine-cpp-types.js > all-spine-types.json
|
||||
|
||||
# Query specific type
|
||||
extract-spine-cpp-types.js > types.json
|
||||
jq '."spine/Bone.h"[] | select(.name == "Bone")' types.json
|
||||
`);
|
||||
}
|
||||
|
||||
function extractLocalTypes(headerFile, typeMap = null) {
|
||||
const absHeaderPath = path.resolve(headerFile);
|
||||
const sourceContent = fs.readFileSync(absHeaderPath, 'utf8');
|
||||
const sourceLines = sourceContent.split('\n');
|
||||
|
||||
// Get AST from clang
|
||||
const cmd = `clang++ -Xclang -ast-dump=json -fsyntax-only -std=c++11 -I "${spineIncludeDir}" "${absHeaderPath}" 2>/dev/null`;
|
||||
const maxBuffer = headerFile.includes('Debug.h') ? 500 : 200; // MB
|
||||
|
||||
let astJson;
|
||||
try {
|
||||
const output = execSync(cmd, { encoding: 'utf8', maxBuffer: maxBuffer * 1024 * 1024 });
|
||||
astJson = JSON.parse(output);
|
||||
} catch (error) {
|
||||
throw new Error(error.code === 'ENOBUFS'
|
||||
? `AST output too large (>${maxBuffer}MB)`
|
||||
: error.message);
|
||||
}
|
||||
|
||||
const types = [];
|
||||
|
||||
function extractEnumValueFromSource(enumConstNode) {
|
||||
if (!enumConstNode.loc) return undefined;
|
||||
|
||||
const line = sourceLines[enumConstNode.loc.line - 1];
|
||||
if (!line) return undefined;
|
||||
|
||||
// Find enum name and check for '='
|
||||
const nameMatch = line.match(new RegExp(`\\b${enumConstNode.name}\\b`));
|
||||
if (!nameMatch) return undefined;
|
||||
|
||||
const afterName = line.substring(nameMatch.index + enumConstNode.name.length);
|
||||
const equalIndex = afterName.indexOf('=');
|
||||
if (equalIndex === -1) return null; // No explicit value
|
||||
|
||||
// Extract value expression
|
||||
let valueText = afterName.substring(equalIndex + 1);
|
||||
|
||||
// Handle multi-line values
|
||||
let currentLine = enumConstNode.loc.line;
|
||||
while (currentLine < sourceLines.length && !valueText.match(/[,}]/)) {
|
||||
valueText += '\n' + sourceLines[currentLine++];
|
||||
}
|
||||
|
||||
// Extract up to comma or brace
|
||||
const endMatch = valueText.match(/^(.*?)([,}])/s);
|
||||
if (endMatch) valueText = endMatch[1];
|
||||
|
||||
// Clean up
|
||||
return valueText
|
||||
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
||||
.replace(/\s+/g, ' ') // Normalize whitespace
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractReturnType(methodNode) {
|
||||
const fullType = methodNode.type?.qualType || '';
|
||||
const match = fullType.match(/^(.+?)\s*\(/);
|
||||
return match ? match[1].trim() : 'void';
|
||||
}
|
||||
|
||||
function extractParameters(methodNode) {
|
||||
return (methodNode.inner || [])
|
||||
.filter(n => n.kind === 'ParmVarDecl')
|
||||
.map(n => ({
|
||||
name: n.name || '',
|
||||
type: n.type?.qualType || ''
|
||||
}));
|
||||
}
|
||||
|
||||
function isInTargetFile(node) {
|
||||
if (!node.loc) return false;
|
||||
|
||||
const targetPath = path.resolve(headerFile);
|
||||
const loc = node.loc;
|
||||
|
||||
// Check direct file location
|
||||
if (loc.file) return path.resolve(loc.file) === targetPath;
|
||||
|
||||
// Check macro locations
|
||||
for (const locType of ['spellingLoc', 'expansionLoc']) {
|
||||
if (loc[locType]) {
|
||||
if (loc[locType].includedFrom) return false;
|
||||
if (loc[locType].file) return path.resolve(loc[locType].file) === targetPath;
|
||||
}
|
||||
}
|
||||
|
||||
// If included from another file, reject
|
||||
if (loc.includedFrom) return false;
|
||||
|
||||
// No location info - assume it's in the main file
|
||||
return true;
|
||||
}
|
||||
|
||||
function extractTypeInfo(node) {
|
||||
const info = {
|
||||
name: node.name || '',
|
||||
kind: node.kind === 'EnumDecl' ? 'enum' : (node.tagUsed || ''),
|
||||
loc: {
|
||||
line: node.loc?.line || 0,
|
||||
col: node.loc?.col || 0
|
||||
}
|
||||
};
|
||||
|
||||
// Extract base classes
|
||||
if (node.bases?.length > 0) {
|
||||
info.superTypes = node.bases.map(b => b.type?.qualType || '').filter(Boolean);
|
||||
}
|
||||
|
||||
// For enums, extract the values
|
||||
if (node.kind === 'EnumDecl') {
|
||||
info.values = (node.inner || [])
|
||||
.filter(n => n.kind === 'EnumConstantDecl')
|
||||
.map(n => {
|
||||
const enumValue = { name: n.name || '' };
|
||||
const sourceValue = extractEnumValueFromSource(n);
|
||||
|
||||
if (sourceValue === null) {
|
||||
// Implicit value - no value property
|
||||
} else if (sourceValue) {
|
||||
enumValue.value = sourceValue;
|
||||
} else if (n.inner?.length > 0) {
|
||||
enumValue.value = "<<extraction failed>>";
|
||||
}
|
||||
|
||||
return enumValue;
|
||||
});
|
||||
return info;
|
||||
}
|
||||
|
||||
// For classes/structs, extract public members
|
||||
info.members = [];
|
||||
let currentAccess = node.tagUsed === 'struct' ? 'public' : 'private';
|
||||
let hasPureVirtual = false;
|
||||
|
||||
for (const inner of node.inner || []) {
|
||||
if (inner.kind === 'AccessSpecDecl') {
|
||||
currentAccess = inner.access || 'private';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inner.kind === 'FriendDecl' || currentAccess !== 'public') continue;
|
||||
|
||||
const member = extractMember(inner, node);
|
||||
if (member) {
|
||||
info.members.push(member);
|
||||
// Check if this is a pure virtual method
|
||||
if (member.kind === 'method' && member.isPure) {
|
||||
hasPureVirtual = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as abstract if it has pure virtual methods
|
||||
if (hasPureVirtual) {
|
||||
info.isAbstract = true;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
function extractMember(inner, parent) {
|
||||
if (inner.isImplicit) return null;
|
||||
|
||||
switch (inner.kind) {
|
||||
case 'FieldDecl':
|
||||
return {
|
||||
kind: 'field',
|
||||
name: inner.name || '',
|
||||
type: inner.type?.qualType || ''
|
||||
};
|
||||
|
||||
case 'CXXMethodDecl':
|
||||
if (!inner.name) return null;
|
||||
// Skip operators - not needed for C wrapper generation
|
||||
if (inner.name.startsWith('operator')) return null;
|
||||
|
||||
return {
|
||||
kind: 'method',
|
||||
name: inner.name,
|
||||
returnType: extractReturnType(inner),
|
||||
parameters: extractParameters(inner),
|
||||
isStatic: inner.storageClass === 'static',
|
||||
isVirtual: inner.virtual || false,
|
||||
isPure: inner.pure || false
|
||||
};
|
||||
|
||||
case 'CXXConstructorDecl':
|
||||
return {
|
||||
kind: 'constructor',
|
||||
name: inner.name || parent.name || '',
|
||||
parameters: extractParameters(inner)
|
||||
};
|
||||
|
||||
case 'CXXDestructorDecl':
|
||||
// Skip destructors - not needed for C wrapper generation
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node, inSpineNamespace = false) {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
|
||||
// Handle spine namespace
|
||||
if (node.kind === 'NamespaceDecl' && node.name === 'spine') {
|
||||
(node.inner || []).forEach(n => processNode(n, true));
|
||||
return;
|
||||
}
|
||||
|
||||
// Recurse to find spine namespace
|
||||
if (!inSpineNamespace) {
|
||||
(node.inner || []).forEach(n => processNode(n, false));
|
||||
return;
|
||||
}
|
||||
|
||||
// Process type declarations
|
||||
const typeKinds = ['CXXRecordDecl', 'ClassTemplateDecl', 'EnumDecl', 'TypedefDecl', 'TypeAliasDecl'];
|
||||
if (!typeKinds.includes(node.kind)) return;
|
||||
|
||||
// Skip if not in target file or invalid
|
||||
if (!isInTargetFile(node) ||
|
||||
node.isImplicit ||
|
||||
!node.name ||
|
||||
node.name.startsWith('_') ||
|
||||
node.name.includes('<')) return;
|
||||
|
||||
// Skip forward declarations
|
||||
if (node.previousDecl && (!node.inner || node.inner.length === 0)) return;
|
||||
|
||||
// Extract type info
|
||||
if (node.kind === 'ClassTemplateDecl') {
|
||||
const classNode = (node.inner || []).find(n => n.kind === 'CXXRecordDecl');
|
||||
if (classNode) {
|
||||
const typeInfo = extractTypeInfo(classNode);
|
||||
typeInfo.isTemplate = true;
|
||||
|
||||
// Extract template parameters
|
||||
const templateParams = [];
|
||||
for (const inner of node.inner || []) {
|
||||
if (inner.kind === 'TemplateTypeParmDecl' && inner.name) {
|
||||
templateParams.push(inner.name);
|
||||
}
|
||||
}
|
||||
if (templateParams.length > 0) {
|
||||
typeInfo.templateParams = templateParams;
|
||||
}
|
||||
|
||||
types.push(typeInfo);
|
||||
}
|
||||
} else if (node.kind === 'CXXRecordDecl' && node.inner?.length > 0) {
|
||||
types.push(extractTypeInfo(node));
|
||||
} else if (node.kind === 'EnumDecl') {
|
||||
types.push(extractTypeInfo(node));
|
||||
} else if (node.kind === 'TypedefDecl' || node.kind === 'TypeAliasDecl') {
|
||||
types.push(extractTypeInfo(node));
|
||||
}
|
||||
}
|
||||
|
||||
processNode(astJson);
|
||||
|
||||
// Filter out forward declarations and sort by line number
|
||||
const filteredTypes = types
|
||||
.filter(t => !(t.members && t.members.length === 0))
|
||||
.sort((a, b) => a.loc.line - b.loc.line);
|
||||
|
||||
// Add inherited methods if we have a type map
|
||||
if (typeMap) {
|
||||
for (const type of filteredTypes) {
|
||||
if (type.superTypes && type.members) {
|
||||
addInheritedMethods(type, typeMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTypes;
|
||||
}
|
||||
|
||||
function addInheritedMethods(type, typeMap) {
|
||||
const inheritedMethods = [];
|
||||
const ownMethodSignatures = new Set();
|
||||
|
||||
// Build a set of method signatures from this type
|
||||
for (const member of type.members) {
|
||||
if (member.kind === 'method') {
|
||||
const sig = getMethodSignature(member);
|
||||
ownMethodSignatures.add(sig);
|
||||
}
|
||||
}
|
||||
|
||||
// Process each supertype
|
||||
for (const superTypeName of type.superTypes) {
|
||||
// Clean up the supertype name (remove namespaces, etc)
|
||||
const cleanName = superTypeName.replace(/^.*::/, '');
|
||||
|
||||
// Skip SpineObject inheritance - it's just noise
|
||||
if (cleanName === 'SpineObject') continue;
|
||||
|
||||
// Check if this is a template supertype
|
||||
const templateMatch = cleanName.match(/^([^<]+)<(.+)>$/);
|
||||
if (templateMatch) {
|
||||
const templateClassName = templateMatch[1];
|
||||
const templateArgs = templateMatch[2];
|
||||
|
||||
const templateType = typeMap.get(templateClassName);
|
||||
if (templateType && templateType.isTemplate && templateType.members) {
|
||||
// Process template inheritance
|
||||
addTemplateInheritedMethods(
|
||||
type, templateType, templateClassName, templateArgs,
|
||||
inheritedMethods, ownMethodSignatures
|
||||
);
|
||||
} else if (templateType && !templateType.isTemplate) {
|
||||
// Template might not be marked as isTemplate, try anyway
|
||||
addTemplateInheritedMethods(
|
||||
type, templateType, templateClassName, templateArgs,
|
||||
inheritedMethods, ownMethodSignatures
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Non-template supertype
|
||||
const superType = typeMap.get(cleanName);
|
||||
|
||||
if (!superType || !superType.members) continue;
|
||||
|
||||
// Add non-overridden methods from supertype
|
||||
for (const member of superType.members) {
|
||||
if (member.kind === 'method') {
|
||||
const sig = getMethodSignature(member);
|
||||
if (!ownMethodSignatures.has(sig)) {
|
||||
const inheritedMember = { ...member, fromSupertype: cleanName };
|
||||
inheritedMethods.push(inheritedMember);
|
||||
ownMethodSignatures.add(sig); // Prevent duplicates from multiple inheritance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add inherited methods to the type
|
||||
type.members.push(...inheritedMethods);
|
||||
}
|
||||
|
||||
function addTemplateInheritedMethods(
|
||||
type, templateType, templateClassName, templateArgs,
|
||||
inheritedMethods, ownMethodSignatures
|
||||
) {
|
||||
// Parse template arguments (handle multiple args)
|
||||
const argsList = [];
|
||||
let depth = 0;
|
||||
let currentArg = '';
|
||||
|
||||
for (const char of templateArgs) {
|
||||
if (char === '<') depth++;
|
||||
else if (char === '>') depth--;
|
||||
|
||||
if (char === ',' && depth === 0) {
|
||||
argsList.push(currentArg.trim());
|
||||
currentArg = '';
|
||||
} else {
|
||||
currentArg += char;
|
||||
}
|
||||
}
|
||||
if (currentArg.trim()) {
|
||||
argsList.push(currentArg.trim());
|
||||
}
|
||||
|
||||
// Build a mapping of template params to actual types
|
||||
const paramMap = new Map();
|
||||
|
||||
// Use the actual template parameters if we have them
|
||||
if (templateType.templateParams && templateType.templateParams.length === argsList.length) {
|
||||
templateType.templateParams.forEach((param, i) => {
|
||||
paramMap.set(param, argsList[i]);
|
||||
});
|
||||
} else {
|
||||
// Fallback: if we don't have template param info, skip substitution
|
||||
console.error(`Warning: Template ${templateClassName} missing parameter info, skipping substitution`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each member of the template
|
||||
for (const member of templateType.members) {
|
||||
if (member.kind === 'method') {
|
||||
// Skip template constructors - they have weird names like "Pose<P>"
|
||||
if (member.name.includes('<')) continue;
|
||||
|
||||
// Clone the member and substitute template parameters
|
||||
const inheritedMember = JSON.parse(JSON.stringify(member));
|
||||
inheritedMember.fromSupertype = `${templateClassName}<${templateArgs}>`;
|
||||
|
||||
// Replace template parameters in return type
|
||||
if (inheritedMember.returnType) {
|
||||
inheritedMember.returnType = substituteTemplateParams(
|
||||
inheritedMember.returnType, paramMap
|
||||
);
|
||||
}
|
||||
|
||||
// Replace template parameters in parameters
|
||||
if (inheritedMember.parameters) {
|
||||
for (const param of inheritedMember.parameters) {
|
||||
param.type = substituteTemplateParams(param.type, paramMap);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this method is overridden
|
||||
const sig = getMethodSignature(inheritedMember);
|
||||
if (!ownMethodSignatures.has(sig)) {
|
||||
inheritedMethods.push(inheritedMember);
|
||||
ownMethodSignatures.add(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function substituteTemplateParams(typeStr, paramMap) {
|
||||
let result = typeStr;
|
||||
|
||||
// Replace template parameters in order of length (longest first)
|
||||
// to avoid replacing substrings (e.g., V before V1)
|
||||
const sortedParams = Array.from(paramMap.keys()).sort((a, b) => b.length - a.length);
|
||||
|
||||
for (const param of sortedParams) {
|
||||
const regex = new RegExp(`\\b${param}\\b`, 'g');
|
||||
result = result.replace(regex, paramMap.get(param));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getMethodSignature(method) {
|
||||
// Create a signature that identifies method overrides
|
||||
// For virtual methods, just use the name
|
||||
// For non-virtual methods, include parameter types
|
||||
let sig = method.name;
|
||||
if (method.parameters && method.parameters.length > 0) {
|
||||
sig += '(' + method.parameters.map(p => p.type).join(',') + ')';
|
||||
} else {
|
||||
sig += '()';
|
||||
}
|
||||
|
||||
// Add const qualifier if present
|
||||
if (method.returnType && method.returnType.includes('const')) {
|
||||
sig += ' const';
|
||||
}
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
function findAllHeaderFiles() {
|
||||
const headers = [];
|
||||
|
||||
function walkDir(dir) {
|
||||
fs.readdirSync(dir).forEach(file => {
|
||||
const fullPath = path.join(dir, file);
|
||||
if (fs.statSync(fullPath).isDirectory()) {
|
||||
walkDir(fullPath);
|
||||
} else if (file.endsWith('.h') && file !== 'spine.h') {
|
||||
headers.push(fullPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
walkDir(spineIncludeDir);
|
||||
return headers.sort();
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const arg = process.argv[2];
|
||||
|
||||
if (arg === '--help' || arg === '-h') {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (arg) {
|
||||
// Single file mode - no inheritance support
|
||||
if (!fs.existsSync(arg)) {
|
||||
console.error(`Error: File not found: ${arg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const types = extractLocalTypes(arg);
|
||||
if (types.length === 0) {
|
||||
console.error('No types found in the specified file');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(JSON.stringify(types, null, 2));
|
||||
} catch (error) {
|
||||
console.error(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Process all files
|
||||
const allHeaders = findAllHeaderFiles();
|
||||
const allTypes = {};
|
||||
let processed = 0, errors = 0;
|
||||
|
||||
console.error(`Processing ${allHeaders.length} header files...`);
|
||||
|
||||
// First pass: extract all types without inheritance
|
||||
const typeMap = new Map();
|
||||
|
||||
for (const headerFile of allHeaders) {
|
||||
const relPath = path.relative(spineIncludeDir, headerFile);
|
||||
process.stderr.write(`\r\x1b[K Pass 1 - Processing ${++processed}/${allHeaders.length}: ${relPath}...`);
|
||||
|
||||
try {
|
||||
const types = extractLocalTypes(headerFile);
|
||||
if (types.length > 0) {
|
||||
allTypes[relPath] = types;
|
||||
// Build type map
|
||||
for (const type of types) {
|
||||
typeMap.set(type.name, type);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errors++;
|
||||
console.error(`\n ERROR processing ${relPath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: add inherited methods
|
||||
console.error(`\n Pass 2 - Adding inherited methods...`);
|
||||
processed = 0;
|
||||
|
||||
for (const headerFile of allHeaders) {
|
||||
const relPath = path.relative(spineIncludeDir, headerFile);
|
||||
if (!allTypes[relPath]) continue;
|
||||
|
||||
process.stderr.write(`\r\x1b[K Pass 2 - Processing ${++processed}/${Object.keys(allTypes).length}: ${relPath}...`);
|
||||
|
||||
for (const type of allTypes[relPath]) {
|
||||
if (type.superTypes && type.members) {
|
||||
addInheritedMethods(type, typeMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`\n Completed: ${Object.keys(allTypes).length} files processed, ${errors} errors`);
|
||||
console.log(JSON.stringify(allTypes, null, 2));
|
||||
}
|
||||
@ -51,12 +51,7 @@ namespace spine {
|
||||
// Our TextureFilter collides with UE4's TextureFilter in unity builds. We rename
|
||||
// TextureFilter to SpineTextureFilter in UE4.
|
||||
#ifdef SPINE_UE4
|
||||
#define TEXTURE_FILTER_ENUM SpineTextureFilter
|
||||
#else
|
||||
#define TEXTURE_FILTER_ENUM TextureFilter
|
||||
#endif
|
||||
|
||||
enum TEXTURE_FILTER_ENUM {
|
||||
enum SpineTextureFilter {
|
||||
TextureFilter_Unknown,
|
||||
TextureFilter_Nearest,
|
||||
TextureFilter_Linear,
|
||||
@ -66,6 +61,18 @@ namespace spine {
|
||||
TextureFilter_MipMapNearestLinear,
|
||||
TextureFilter_MipMapLinearLinear
|
||||
};
|
||||
#else
|
||||
enum TextureFilter {
|
||||
TextureFilter_Unknown,
|
||||
TextureFilter_Nearest,
|
||||
TextureFilter_Linear,
|
||||
TextureFilter_MipMap,
|
||||
TextureFilter_MipMapNearestNearest,
|
||||
TextureFilter_MipMapLinearNearest,
|
||||
TextureFilter_MipMapNearestLinear,
|
||||
TextureFilter_MipMapLinearLinear
|
||||
};
|
||||
#endif
|
||||
|
||||
enum TextureWrap {
|
||||
TextureWrap_MirroredRepeat,
|
||||
@ -78,8 +85,13 @@ namespace spine {
|
||||
String name;
|
||||
String texturePath;
|
||||
Format format;
|
||||
TEXTURE_FILTER_ENUM minFilter;
|
||||
TEXTURE_FILTER_ENUM magFilter;
|
||||
#ifdef SPINE_UE4
|
||||
SpineTextureFilter minFilter;
|
||||
SpineTextureFilter magFilter;
|
||||
#else
|
||||
TextureFilter minFilter;
|
||||
TextureFilter magFilter;
|
||||
#endif
|
||||
TextureWrap uWrap;
|
||||
TextureWrap vWrap;
|
||||
int width, height;
|
||||
|
||||
@ -32,8 +32,7 @@
|
||||
|
||||
#include <spine/Extension.h>
|
||||
#include <spine/Vector.h>
|
||||
|
||||
#include <map>
|
||||
#include <spine/HashMap.h>
|
||||
|
||||
namespace spine {
|
||||
|
||||
@ -57,12 +56,14 @@ namespace spine {
|
||||
}
|
||||
|
||||
void reportLeaks() {
|
||||
for (std::map<void *, Allocation>::iterator it = _allocated.begin(); it != _allocated.end(); it++) {
|
||||
printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size,
|
||||
it->second.address);
|
||||
HashMap<void *, Allocation>::Entries entries = _allocated.getEntries();
|
||||
while (entries.hasNext()) {
|
||||
HashMap<void *, Allocation>::Pair pair = entries.next();
|
||||
printf("\"%s:%i (%zu bytes at %p)\n", pair.value.fileName, pair.value.line, pair.value.size,
|
||||
pair.value.address);
|
||||
}
|
||||
printf("allocations: %zu, reallocations: %zu, frees: %zu\n", _allocations, _reallocations, _frees);
|
||||
if (_allocated.empty()) printf("No leaks detected\n");
|
||||
if (_allocated.size() == 0) printf("No leaks detected\n");
|
||||
}
|
||||
|
||||
void clearAllocations() {
|
||||
@ -72,7 +73,7 @@ namespace spine {
|
||||
|
||||
virtual void *_alloc(size_t size, const char *file, int line) {
|
||||
void *result = _extension->_alloc(size, file, line);
|
||||
_allocated[result] = Allocation(result, size, file, line);
|
||||
_allocated.put(result, Allocation(result, size, file, line));
|
||||
_allocations++;
|
||||
_usedMemory += size;
|
||||
return result;
|
||||
@ -80,28 +81,46 @@ namespace spine {
|
||||
|
||||
virtual void *_calloc(size_t size, const char *file, int line) {
|
||||
void *result = _extension->_calloc(size, file, line);
|
||||
_allocated[result] = Allocation(result, size, file, line);
|
||||
_allocated.put(result, Allocation(result, size, file, line));
|
||||
_allocations++;
|
||||
_usedMemory += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual void *_realloc(void *ptr, size_t size, const char *file, int line) {
|
||||
if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size;
|
||||
_allocated.erase(ptr);
|
||||
if (_allocated.containsKey(ptr)) {
|
||||
// Find and store the size before removing
|
||||
HashMap<void *, Allocation>::Entries entries = _allocated.getEntries();
|
||||
while (entries.hasNext()) {
|
||||
HashMap<void *, Allocation>::Pair pair = entries.next();
|
||||
if (pair.key == ptr) {
|
||||
_usedMemory -= pair.value.size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_allocated.remove(ptr);
|
||||
}
|
||||
void *result = _extension->_realloc(ptr, size, file, line);
|
||||
_reallocations++;
|
||||
_allocated[result] = Allocation(result, size, file, line);
|
||||
_allocated.put(result, Allocation(result, size, file, line));
|
||||
_usedMemory += size;
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual void _free(void *mem, const char *file, int line) {
|
||||
if (_allocated.count(mem)) {
|
||||
if (_allocated.containsKey(mem)) {
|
||||
_extension->_free(mem, file, line);
|
||||
_frees++;
|
||||
_usedMemory -= _allocated[mem].size;
|
||||
_allocated.erase(mem);
|
||||
// Find and store the size before removing
|
||||
HashMap<void *, Allocation>::Entries entries = _allocated.getEntries();
|
||||
while (entries.hasNext()) {
|
||||
HashMap<void *, Allocation>::Pair pair = entries.next();
|
||||
if (pair.key == mem) {
|
||||
_usedMemory -= pair.value.size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_allocated.remove(mem);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -112,8 +131,8 @@ namespace spine {
|
||||
virtual char *_readFile(const String &path, int *length) {
|
||||
auto data = _extension->_readFile(path, length);
|
||||
|
||||
if (_allocated.count(data) == 0) {
|
||||
_allocated[data] = Allocation(data, sizeof(char) * (*length), nullptr, 0);
|
||||
if (!_allocated.containsKey(data)) {
|
||||
_allocated.put(data, Allocation(data, sizeof(char) * (*length), nullptr, 0));
|
||||
_allocations++;
|
||||
_usedMemory += sizeof(char) * (*length);
|
||||
}
|
||||
@ -127,7 +146,7 @@ namespace spine {
|
||||
|
||||
private:
|
||||
SpineExtension *_extension;
|
||||
std::map<void *, Allocation> _allocated;
|
||||
HashMap<void *, Allocation> _allocated;
|
||||
size_t _allocations;
|
||||
size_t _reallocations;
|
||||
size_t _frees;
|
||||
|
||||
@ -127,7 +127,7 @@ namespace spine {
|
||||
|
||||
void setScale(float scale) { _scale = scale; }
|
||||
|
||||
String &getError() { return _error; }
|
||||
const String &getError() const { return _error; }
|
||||
|
||||
private:
|
||||
struct DataInput : public SpineObject {
|
||||
|
||||
@ -79,7 +79,7 @@ namespace spine {
|
||||
|
||||
void setScale(float scale) { _scale = scale; }
|
||||
|
||||
String &getError() { return _error; }
|
||||
const String &getError() const { return _error; }
|
||||
|
||||
private:
|
||||
AttachmentLoader *_attachmentLoader;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user