diff --git a/.gitignore b/.gitignore index 23a6f1f..0b6684f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ ## NOTHING IN HERE FOR NOW -/build-steps/local-build-steps-windows.txt +/build-steps/local-build-steps/** /outputs /godot-rj-nuget-package /godot/modules/rokojori_action_library + diff --git a/cpp-class-member.js b/cpp-class-member.js new file mode 100644 index 0000000..203d985 --- /dev/null +++ b/cpp-class-member.js @@ -0,0 +1,146 @@ +class CppParameter +{ + name = ""; + type = ""; +} + +class CppClassMember +{ + name = ""; + isMethod = false; + isVirtual = false; + isStatic = false; + type = "void"; + parameters = []; + + constructor( nameDefinition, body ) + { + if ( nameDefinition.endsWith( "()" ) ) + { + this.isMethod = true; + nameDefinition = nameDefinition.replace( /\(\)$/, "" ); + } + + let names = nameDefinition.split( /\s+/ ); + this.name = names[ names.length -1 ]; + + for ( let i = 0; i < names.length - 1; i++ ) + { + if ( "virtual" === names[ i ] ) + { + this.isVirtual = true; + } + + if ( "static" === names[ i ] ) + { + this.isStatic = true; + } + + } + + if ( this.isMethod ) + { + this.parseMethodBody( body ); + } + } + + parseMethodBody( body ) + { + if ( ! body ) + { + return; + } + + this.type = body.type || "void"; + + let parameters = body.parameters; + + if ( ! parameters ) + { + return; + } + + for ( let i = 0; i < parameters.length; i++ ) + { + let pm = parameters[ i ]; + + let cppParameter = new CppParameter(); + + for ( let it in pm ) + { + if ( ! pm.hasOwnProperty( it ) ) + { + continue; + } + + cppParameter.name = it; + cppParameter.type = pm[ it ]; + } + + this.parameters.push( cppParameter ); + } + } + + getMethodBinding( className ) + { + if ( this.isVirtual ) + { + return `GDVIRTUAL_BIND( ${this.name} );`; + } + else + { + return `ClassDB::bind_method( D_METHOD( "${this.name}" ) , &${className}::${this.name} );` + } + } + + getHeaderDefinition() + { + if ( this.isMethod ) + { + return this.getMethodHeaderDefinition(); + } + else + { + return this.getVariableHeaderDefinition(); + } + } + + getParametersDefinition() + { + return this.parameters.map( p => p.type + " " + p.name ).join( ", " ); + } + + getMethodHeaderDefinition() + { + if ( this.isVirtual ) + { + let numParameters = this.parameters.length; + let methodReturnType = this.type === "void" ? "" : "R"; + let returnTypeDefinition = this.type === "void" ? "" : ( this.type + ", " ) ; + + let parametersDefinition = ""; + + if ( numParameters > 0 ) + { + parametersDefinition = ", " + this.parameters.map( p => p.type ).join( ", " ); + } + + + return `GDVIRTUAL${numParameters}${methodReturnType}( ${returnTypeDefinition}${this.name}${parametersDefinition} );` + } + + return `${this.type} ${this.name}(${this.parametersDefinition});` + } + + getVariableHeaderDefinition() + { + return `${this.type} ${this.name};` + } + +} + +module.exports = +{ + CppParameter, + CppClassMember +} \ No newline at end of file diff --git a/create-cpp.js b/create-cpp.js new file mode 100644 index 0000000..16ef88a --- /dev/null +++ b/create-cpp.js @@ -0,0 +1,252 @@ +const fs = require( "node:fs/promises" ); +const path = require( "path" ); +const { loadJSON, getFiles, saveUTF8, getMatches, loadUTF8, insertText } = require( "./library.js" ); +const { CppClassMember } = require( "./cpp-class-member.js" ); + +function createCppHeader( className, extendingClassName, headerDefine, protectedMembers, publicMembers ) +{ + +let cppHeader = + +` +/* ${className}.h */ + +#ifndef ${headerDefine} +#define ${headerDefine} + +#include "./${extendingClassName}.h" + + +class ${className} : public ${extendingClassName} +{ + GDCLASS(${className}, ${extendingClassName}); + +protected: + static void _bind_methods(); + ${protectedMembers} + +public: + + ${publicMembers} + + ${className}(); + + ~${className}(); +}; + + +#endif // ${headerDefine} +` + +return cppHeader; + + +} + +function createCppImplementation( className, boundMethods ) +{ + +let cppImplementation = +` +/* ${className}.cpp */ + +#include "${className}.h" + + +void ${className}::_bind_methods() +{ + ${boundMethods} +} + +${className}::${className}() +{ + +} + +${className}::~${className}() +{ + +} + +` + +return cppImplementation; + +} + +let definitionsPath = __dirname + "/rokojori-action-library-definitions"; +let outputPath = __dirname + "/rokojori-action-library"; +let registerTypesPath = outputPath + "/register_types.cpp"; +let classRegistrationRegex = /ClassDB\:\:register_class\<(\w+)\>\(\)\;/; + +function createTypeRegistration( className ) +{ + return `ClassDB::register_class<${className}>();`; +} + +function createIncludes( className ) +{ + return `#include "./${className}.h"`; +} + +async function main() +{ + let files = await getFiles( definitionsPath ); + let typesFile = await loadUTF8( registerTypesPath ); + + let types = getRegistratedTypes( typesFile ); + + let missingTypes = []; + + for ( let file of files ) + { + await createCppFiles( file, types, missingTypes ); + } + + if ( missingTypes.length === 0 ) + { + return; + } + + console.log( "Registrating missing types:", missingTypes ); + + let additions = missingTypes.map( mt => createTypeRegistration( mt ) ); + let additionsBlock = additions.join( "\n " ); + + + + + + + let lastType = types[ types.length -1 ]; + let insertPosition = lastType.index + lastType.match.length; + let updatedTypesFile = insertText( typesFile, "\n " + additionsBlock, insertPosition ); + + + let includeMatches = getMatches( updatedTypesFile, /#include "(\w|\.|\/|\\)+.h"/ ); + let lastInclude = includeMatches[ includeMatches.length - 1 ]; + let includes = missingTypes.map( mt => createIncludes( mt ) ).join( "\n" ); + insertPosition = lastInclude.index + lastInclude.match.length; + updatedTypesFile = insertText( updatedTypesFile, "\n" + includes, insertPosition ); + + saveUTF8( registerTypesPath, updatedTypesFile ); + +} + + + +function grabMembers( membersData ) +{ + let members = []; + + if ( ! membersData ) + { + return members; + } + + for ( let it in membersData ) + { + if ( ! membersData.hasOwnProperty( it ) ) + { + continue; + } + + let nameDefinition = it; + let memberData = membersData[ it ]; + + let classMember = new CppClassMember( nameDefinition, memberData ); + + members.push( classMember ); + } + + return members; +} + +function getHeaderDefine( className ) +{ + className = className.replace( /^RJ/, "" ); + + let output = [ className[ 0 ] ]; + + for ( let i = 1; i < className.length; i++ ) + { + let character = className[ i ]; + let upperCaseCharacter = character.toUpperCase(); + + if ( character === upperCaseCharacter ) + { + output.push( "_" ); + + } + + output.push( upperCaseCharacter ); + + } + + className = output.join( "" ); + + return "ROKOJORI_CORE__" + className + "_H"; +} + +function getRegistratedTypes( text ) +{ + let matches = getMatches( text, classRegistrationRegex ); + + let types = matches.map( + m => + { + return { match: m.match, index: m.index, type: classRegistrationRegex.exec( m.match )[ 1 ] } + } + ); + + return types; + +} + + + +async function createCppFiles( definitionPath, types, missingTypes ) +{ + console.log( "Creating: ", definitionPath ); + + let data = await loadJSON( path.join( definitionsPath, definitionPath ) ); + + let classNameResult = /(\w+)(?:\:(\w+))?/.exec( data.class ); + + let className = classNameResult[ 1 ]; + let extendingClass = classNameResult[ 2 ]; + + let headerDefine = getHeaderDefine( className ); + + let protectedMembers = grabMembers( data.protected ); + let publicMembers = grabMembers( data.public ); + + let protectedHeader = protectedMembers.map( p => p.getHeaderDefinition() ).join( "\n " ); + let publicHeader = publicMembers.map( p => p.getHeaderDefinition() ).join( "\n " ); + + let protectedBindings = protectedMembers.map( p => p.getMethodBinding( className ) ); + let publicBindings = publicMembers.map( p => p.getMethodBinding( className ) ); + + let bindings = protectedBindings.concat( publicBindings ).join( "\n " ); + + let header = createCppHeader( className, extendingClass, headerDefine, protectedHeader, publicHeader ); + let implementation = createCppImplementation( className, bindings ); + + //console.log( header ); + //console.log( implementation ); + + let hasType = types.find( t => t.type === className ); + + if ( ! hasType ) + { + missingTypes.push( className ); + } + + let rawFilePath = path.join( outputPath, className ); + await saveUTF8( rawFilePath + ".h", header ); + await saveUTF8( rawFilePath + ".cpp", implementation ); +} + + + +main(); \ No newline at end of file diff --git a/library.js b/library.js index fabffe0..799eca3 100644 --- a/library.js +++ b/library.js @@ -1,6 +1,11 @@ const { stat } = require("node:fs"); const fs = require( "node:fs/promises" ); +async function getFiles( filePath ) +{ + return await fs.readdir( filePath ); +} + async function exists( filePath ) { try @@ -34,6 +39,87 @@ async function loadUTF8( filePath ) } +async function saveUTF8( filePath, data ) +{ + try + { + await fs.writeFile( filePath, data ); + return Promise.resolve( true ); + } + catch ( exception ) + { + console.warn( exception ); + } + + return Promise.resolve( false ); + +} + +function makeSticky( regexp ) +{ + if ( regexp.sticky ) + { + return regexp; + } + + var source = regexp.source; + var flags = regexp.flags; + + if ( flags.indexOf( "y" ) === -1 ) + { + flags += "y"; + } + + return new RegExp( source, flags ); +} + +function makeGlobal( regexp ) +{ + if ( regexp.global ) + { + return regexp; + } + + var source = regexp.source; + var flags = regexp.flags; + + if ( flags.indexOf( "g" ) === -1 ) + { + flags += "g"; + } + + return new RegExp( source, flags ); +} + +function getMatches( source, regex ) +{ + regex = makeGlobal( regex ); + + let offset = 0; + + let matches = []; + + while ( offset < source.length ) + { + regex.lastIndex = offset; + let result = regex.exec( source ); + + let match = result ? result[ 0 ] : null; + + if ( result && result.index != offset && match.length > 0) + { + offset = result.index + match.length; + matches.push( { match:match, index: result.index } ); + } + else + { + return matches; + } + } + + return matches; +} + async function loadJSON( filePath ) { let text = await loadUTF8( filePath ); @@ -57,9 +143,21 @@ async function loadJSON( filePath ) return Promise.resolve( null ); } +function insertText( sourceText, insertText, insertPosition ) +{ + let before = sourceText.substring( 0, insertPosition ); + let after = sourceText.substring( insertPosition, sourceText.length ); + + return before + insertText + after; +} + module.exports = { exists, loadUTF8, - loadJSON + saveUTF8, + loadJSON, + getFiles, + getMatches, + insertText } \ No newline at end of file diff --git a/rokojori-action-library b/rokojori-action-library index c040dca..26aea1a 160000 --- a/rokojori-action-library +++ b/rokojori-action-library @@ -1 +1 @@ -Subproject commit c040dcae242ec1bbd17906b42092b614e483fb09 +Subproject commit 26aea1a20d5bc2e35486517e2849b02f3b5d585b diff --git a/rokojori-action-library-definitions/RJSequenceAction.json b/rokojori-action-library-definitions/RJSequenceAction.json new file mode 100644 index 0000000..d098989 --- /dev/null +++ b/rokojori-action-library-definitions/RJSequenceAction.json @@ -0,0 +1,10 @@ +{ + "class":"RJSequenceAction:RJAction", + + "public": + { + "virtual dispatchStartEvent()":{}, + "virtual dispatchCancelEvent()":{}, + "virtual dispatchEndEvent()":{} + } +} \ No newline at end of file diff --git a/rokojori-action-library-definitions/RJUpdatable.json b/rokojori-action-library-definitions/RJUpdatable.json new file mode 100644 index 0000000..437ea5b --- /dev/null +++ b/rokojori-action-library-definitions/RJUpdatable.json @@ -0,0 +1,11 @@ +{ + "class":"RJUpdatable:RJNetworkNode", + + "public": + { + "virtual update()": + { + "parameters":[ { "delta":"double" } ] + } + } +} \ No newline at end of file