mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +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
|
spine-cpp/.cache
|
||||||
.build
|
.build
|
||||||
spine-libgdx/.settings
|
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
|
// Our TextureFilter collides with UE4's TextureFilter in unity builds. We rename
|
||||||
// TextureFilter to SpineTextureFilter in UE4.
|
// TextureFilter to SpineTextureFilter in UE4.
|
||||||
#ifdef SPINE_UE4
|
#ifdef SPINE_UE4
|
||||||
#define TEXTURE_FILTER_ENUM SpineTextureFilter
|
enum SpineTextureFilter {
|
||||||
#else
|
|
||||||
#define TEXTURE_FILTER_ENUM TextureFilter
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum TEXTURE_FILTER_ENUM {
|
|
||||||
TextureFilter_Unknown,
|
TextureFilter_Unknown,
|
||||||
TextureFilter_Nearest,
|
TextureFilter_Nearest,
|
||||||
TextureFilter_Linear,
|
TextureFilter_Linear,
|
||||||
@ -66,6 +61,18 @@ namespace spine {
|
|||||||
TextureFilter_MipMapNearestLinear,
|
TextureFilter_MipMapNearestLinear,
|
||||||
TextureFilter_MipMapLinearLinear
|
TextureFilter_MipMapLinearLinear
|
||||||
};
|
};
|
||||||
|
#else
|
||||||
|
enum TextureFilter {
|
||||||
|
TextureFilter_Unknown,
|
||||||
|
TextureFilter_Nearest,
|
||||||
|
TextureFilter_Linear,
|
||||||
|
TextureFilter_MipMap,
|
||||||
|
TextureFilter_MipMapNearestNearest,
|
||||||
|
TextureFilter_MipMapLinearNearest,
|
||||||
|
TextureFilter_MipMapNearestLinear,
|
||||||
|
TextureFilter_MipMapLinearLinear
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
enum TextureWrap {
|
enum TextureWrap {
|
||||||
TextureWrap_MirroredRepeat,
|
TextureWrap_MirroredRepeat,
|
||||||
@ -78,8 +85,13 @@ namespace spine {
|
|||||||
String name;
|
String name;
|
||||||
String texturePath;
|
String texturePath;
|
||||||
Format format;
|
Format format;
|
||||||
TEXTURE_FILTER_ENUM minFilter;
|
#ifdef SPINE_UE4
|
||||||
TEXTURE_FILTER_ENUM magFilter;
|
SpineTextureFilter minFilter;
|
||||||
|
SpineTextureFilter magFilter;
|
||||||
|
#else
|
||||||
|
TextureFilter minFilter;
|
||||||
|
TextureFilter magFilter;
|
||||||
|
#endif
|
||||||
TextureWrap uWrap;
|
TextureWrap uWrap;
|
||||||
TextureWrap vWrap;
|
TextureWrap vWrap;
|
||||||
int width, height;
|
int width, height;
|
||||||
|
|||||||
@ -32,8 +32,7 @@
|
|||||||
|
|
||||||
#include <spine/Extension.h>
|
#include <spine/Extension.h>
|
||||||
#include <spine/Vector.h>
|
#include <spine/Vector.h>
|
||||||
|
#include <spine/HashMap.h>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace spine {
|
namespace spine {
|
||||||
|
|
||||||
@ -57,12 +56,14 @@ namespace spine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void reportLeaks() {
|
void reportLeaks() {
|
||||||
for (std::map<void *, Allocation>::iterator it = _allocated.begin(); it != _allocated.end(); it++) {
|
HashMap<void *, Allocation>::Entries entries = _allocated.getEntries();
|
||||||
printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size,
|
while (entries.hasNext()) {
|
||||||
it->second.address);
|
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);
|
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() {
|
void clearAllocations() {
|
||||||
@ -72,7 +73,7 @@ namespace spine {
|
|||||||
|
|
||||||
virtual void *_alloc(size_t size, const char *file, int line) {
|
virtual void *_alloc(size_t size, const char *file, int line) {
|
||||||
void *result = _extension->_alloc(size, file, line);
|
void *result = _extension->_alloc(size, file, line);
|
||||||
_allocated[result] = Allocation(result, size, file, line);
|
_allocated.put(result, Allocation(result, size, file, line));
|
||||||
_allocations++;
|
_allocations++;
|
||||||
_usedMemory += size;
|
_usedMemory += size;
|
||||||
return result;
|
return result;
|
||||||
@ -80,28 +81,46 @@ namespace spine {
|
|||||||
|
|
||||||
virtual void *_calloc(size_t size, const char *file, int line) {
|
virtual void *_calloc(size_t size, const char *file, int line) {
|
||||||
void *result = _extension->_calloc(size, file, line);
|
void *result = _extension->_calloc(size, file, line);
|
||||||
_allocated[result] = Allocation(result, size, file, line);
|
_allocated.put(result, Allocation(result, size, file, line));
|
||||||
_allocations++;
|
_allocations++;
|
||||||
_usedMemory += size;
|
_usedMemory += size;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void *_realloc(void *ptr, size_t size, const char *file, int line) {
|
virtual void *_realloc(void *ptr, size_t size, const char *file, int line) {
|
||||||
if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size;
|
if (_allocated.containsKey(ptr)) {
|
||||||
_allocated.erase(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);
|
void *result = _extension->_realloc(ptr, size, file, line);
|
||||||
_reallocations++;
|
_reallocations++;
|
||||||
_allocated[result] = Allocation(result, size, file, line);
|
_allocated.put(result, Allocation(result, size, file, line));
|
||||||
_usedMemory += size;
|
_usedMemory += size;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void _free(void *mem, const char *file, int line) {
|
virtual void _free(void *mem, const char *file, int line) {
|
||||||
if (_allocated.count(mem)) {
|
if (_allocated.containsKey(mem)) {
|
||||||
_extension->_free(mem, file, line);
|
_extension->_free(mem, file, line);
|
||||||
_frees++;
|
_frees++;
|
||||||
_usedMemory -= _allocated[mem].size;
|
// Find and store the size before removing
|
||||||
_allocated.erase(mem);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,8 +131,8 @@ namespace spine {
|
|||||||
virtual char *_readFile(const String &path, int *length) {
|
virtual char *_readFile(const String &path, int *length) {
|
||||||
auto data = _extension->_readFile(path, length);
|
auto data = _extension->_readFile(path, length);
|
||||||
|
|
||||||
if (_allocated.count(data) == 0) {
|
if (!_allocated.containsKey(data)) {
|
||||||
_allocated[data] = Allocation(data, sizeof(char) * (*length), nullptr, 0);
|
_allocated.put(data, Allocation(data, sizeof(char) * (*length), nullptr, 0));
|
||||||
_allocations++;
|
_allocations++;
|
||||||
_usedMemory += sizeof(char) * (*length);
|
_usedMemory += sizeof(char) * (*length);
|
||||||
}
|
}
|
||||||
@ -127,7 +146,7 @@ namespace spine {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
SpineExtension *_extension;
|
SpineExtension *_extension;
|
||||||
std::map<void *, Allocation> _allocated;
|
HashMap<void *, Allocation> _allocated;
|
||||||
size_t _allocations;
|
size_t _allocations;
|
||||||
size_t _reallocations;
|
size_t _reallocations;
|
||||||
size_t _frees;
|
size_t _frees;
|
||||||
|
|||||||
@ -127,7 +127,7 @@ namespace spine {
|
|||||||
|
|
||||||
void setScale(float scale) { _scale = scale; }
|
void setScale(float scale) { _scale = scale; }
|
||||||
|
|
||||||
String &getError() { return _error; }
|
const String &getError() const { return _error; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct DataInput : public SpineObject {
|
struct DataInput : public SpineObject {
|
||||||
|
|||||||
@ -79,7 +79,7 @@ namespace spine {
|
|||||||
|
|
||||||
void setScale(float scale) { _scale = scale; }
|
void setScale(float scale) { _scale = scale; }
|
||||||
|
|
||||||
String &getError() { return _error; }
|
const String &getError() const { return _error; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AttachmentLoader *_attachmentLoader;
|
AttachmentLoader *_attachmentLoader;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user