/** * Generate the code from events (using GDJS platform) * and create a JavaScript function that runs it. * * The JavaScript function must be called with the `runtimeScene` to be used. * In this context, GDJS game engine does not exist, so you must pass a mock * to it to validate that the events are working properly. */ function generateCompiledEventsForEventsFunction( gd, project, eventsFunction, options = {}, ) { const extension = new gd.EventsFunctionsExtension(); const runCompiledEventsFunction = generateCompiledEventsForEventsFunctionWithContext( gd, project, extension, eventsFunction, options ); extension.delete(); return runCompiledEventsFunction; } /** * Generate the code from events (using GDJS platform) * and create a JavaScript function that runs it. * * The JavaScript function must be called with the `runtimeScene` to be used. * In this context, GDJS game engine does not exist, so you must pass a mock * to it to validate that the events are working properly. */ function generateCompiledEventsForEventsFunctionWithContext( gd, project, extension, eventsFunction, options = {} ) { const namespace = 'functionNamespace'; const { exceptionallyPersistNamespaceOnGdjs } = options; const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(project); const includeFiles = new gd.SetString(); const code = eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( extension, eventsFunction, namespace, includeFiles, true ); eventsFunctionsExtensionCodeGenerator.delete(); includeFiles.delete(); const fullCode = '"use strict";\n' + // Expose some global variables that are expected by the generated code: `const Hashtable = gdjs.Hashtable;\n` + (exceptionallyPersistNamespaceOnGdjs ? `let functionNamespace = gdjs.__persistingTestFunctionNamespace || { registeredGdjsCallbacks: [] };\n` : `let functionNamespace = { registeredGdjsCallbacks: [] };\n`) + code + // Persist the namespace on gdjs so hot-reload tests can find it. (exceptionallyPersistNamespaceOnGdjs ? `;\ngdjs.__persistingTestFunctionNamespace = functionNamespace;` : '') + // Return the function for it to be called (if arguments are passed). `; return functionArguments ? functionNamespace.func.apply(functionNamespace.func, [runtimeScene, ...functionArguments, runtimeScene]) : null;`; if (options.logCode) console.log(fullCode); // Create a "real" JavaScript function with the generated code. const runCompiledEventsFunction = new Function( 'gdjs', 'runtimeScene', 'functionArguments', fullCode ); return runCompiledEventsFunction; } const generatedEventsCodeToJSFunction = (code, gdjs, runtimeScene) => { const func = new Function( 'gdjs', 'runtimeScene', 'functionArguments', '"use strict";\n' + // Expose some global variables that are expected by the generated code: `const Hashtable = gdjs.Hashtable;` + `let functionNamespace = { registeredGdjsCallbacks: [] };` + '\n' + code + // Return the function for it to be called (if arguments are passed). `; return functionNamespace.func(runtimeScene, ...functionArguments, runtimeScene);` ); return (...args) => func(gdjs, runtimeScene, args); }; /** * @param {*} gd * @param {gdProject} project * @param {gdEventsFunctionsExtension} eventsFunctionsExtension * @param {gdEventsBasedBehavior} eventsBasedBehavior * @param {*} gdjs * @param {{logCode: boolean}} options * @returns */ function generateCompiledEventsForEventsBasedBehavior( gd, project, eventsFunctionsExtension, eventsBasedBehavior, gdjs, options = {} ) { const includeFiles = new gd.SetString(); const codeNamespace = 'behaviorNamespace'; const behaviorCodeGenerator = new gd.BehaviorCodeGenerator(project); // Generate "mangled names" as required by the code generation const behaviorMethodMangledNames = new gd.MapStringString(); for ( let i = 0; i < eventsBasedBehavior.getEventsFunctions().getEventsFunctionsCount(); i++ ) { const eventsFunction = eventsBasedBehavior .getEventsFunctions() .getEventsFunctionAt(i); behaviorMethodMangledNames.set( eventsFunction.getName(), eventsFunction.getName() ); } const code = behaviorCodeGenerator.generateRuntimeBehaviorCompleteCode( eventsFunctionsExtension, eventsBasedBehavior, codeNamespace, behaviorMethodMangledNames, includeFiles, true ); if (options.logCode) { console.log(code); } // Create a function returning the generated behavior. const compiledBehavior = new Function( 'gdjs', `"use strict"; let behaviorNamespace = {}; let Hashtable = gdjs.Hashtable; ${code} return behaviorNamespace.${eventsBasedBehavior.getName()};` )(gdjs); includeFiles.delete(); behaviorCodeGenerator.delete(); behaviorMethodMangledNames.delete(); return compiledBehavior; } /** * @param {*} gd * @param {gdProject} project * @param {gdEventsFunctionsExtension} eventsFunctionsExtension * @param {gdEventsBasedObject} eventsBasedObject * @param {*} gdjs * @param {{logCode: boolean}} options * @returns */ function generateCompiledEventsForEventsBasedObject( gd, project, eventsFunctionsExtension, eventsBasedObject, gdjs, options = {} ) { const includeFiles = new gd.SetString(); const codeNamespace = 'objectNamespace'; const objectCodeGenerator = new gd.ObjectCodeGenerator(project); // Generate "mangled names" as required by the code generation const objectMethodMangledNames = new gd.MapStringString(); const eventsFunctionsContainer = eventsBasedObject.getEventsFunctions(); for (let i = 0; i < eventsFunctionsContainer.getEventsFunctionsCount(); i++) { const eventsFunction = eventsFunctionsContainer.getEventsFunctionAt(i); objectMethodMangledNames.set( eventsFunction.getName(), eventsFunction.getName() ); } const code = objectCodeGenerator.generateRuntimeObjectCompleteCode( eventsFunctionsExtension, eventsBasedObject, codeNamespace, objectMethodMangledNames, includeFiles, true ); if (options.logCode) { console.log(code); } objectCodeGenerator.delete(); includeFiles.delete(); objectMethodMangledNames.delete(); // Create a function returning the generated object. const compiledObject = new Function( 'gdjs', `"use strict"; let objectNamespace = {}; const Hashtable = gdjs.Hashtable; ${code} return objectNamespace.${eventsBasedObject.getName()};` )(gdjs); return compiledObject; } /** * Generates all functions, behaviors and objects of an EventsFunctionsExtension. * * Usage: * Pass in the extension as a JS object. You will get in return an object * `{ freeFunctions: { [functionName]: FreeEventsFunction }, behaviors: { [behaviorName]: RuntimeBehavior }, objects: { [objectName]: RuntimeObject } }` * * Example: * ```js * const extension = require("./extensions/Extension.json"): * const { freeFunctions } = generateCompiledEventsForSerializedEventsBasedExtension(extension); * const { RGBToHex } = freeFunctions; * const result = RGBToHex(gdjs, runtimeScene, ["1;2;3"]) * ``` */ function generateCompiledEventsForSerializedEventsBasedExtension( gd, serializedEventsFunctionsExtension, gdjs, runtimeScene ) { const project = new gd.ProjectHelper.createNewGDJSProject(); const extension = project.insertNewEventsFunctionsExtension( serializedEventsFunctionsExtension.name, 0 ); const serializerElement = gd.Serializer.fromJSObject( serializedEventsFunctionsExtension ); extension.unserializeFrom(project, serializerElement); serializerElement.delete(); const includeFiles = new gd.SetString(); const codeNamespace = 'functionNamespace'; const generatedExtensionModule = { freeFunctions: {}, behaviors: {}, objects: {}, }; const eventsFunctionsExtensionCodeGenerator = new gd.EventsFunctionsExtensionCodeGenerator(project); const freeEventsFunctions = extension.getEventsFunctions(); for (let i = 0; i < freeEventsFunctions.getEventsFunctionsCount(); i++) { const eventsFunction = freeEventsFunctions.getEventsFunctionAt(i); generatedExtensionModule.freeFunctions[eventsFunction.getName()] = generatedEventsCodeToJSFunction( eventsFunctionsExtensionCodeGenerator.generateFreeEventsFunctionCompleteCode( extension, eventsFunction, codeNamespace, includeFiles, true ), gdjs, runtimeScene ); } eventsFunctionsExtensionCodeGenerator.delete(); const behaviorsList = extension.getEventsBasedBehaviors(); for (let i = 0; i < behaviorsList.getCount(); i++) { const behavior = behaviorsList.getAt(i); generatedExtensionModule.behaviors[behavior.getName()] = generateCompiledEventsForEventsBasedBehavior( gd, project, extension, behavior, gdjs ); } const objectsLists = extension.getEventsBasedObjects(); for (let i = 0; i < objectsLists.getCount(); i++) { const obj = objectsLists.getAt(i); generatedExtensionModule.objects[obj.getName()] = generateCompiledEventsForEventsBasedObject( gd, project, extension, obj, gdjs ); } includeFiles.delete(); project.delete(); return generatedExtensionModule; } /** * Helper to create compiled events from serialized events, creating a project and the events function. * @param {*} gd * @param {gdSerializerElement} eventsSerializerElement * @param {{parameterTypes: {[name: string]: string}, groups: {[name: string]: string[]}, logCode: boolean}?} configuration * @returns */ function generateCompiledEventsFromSerializedEvents( gd, eventsSerializerElement, configuration ) { const project = new gd.ProjectHelper.createNewGDJSProject(); const eventsFunction = new gd.EventsFunction(); eventsFunction.getEvents().unserializeFrom(project, eventsSerializerElement); if (configuration) { const { parameterTypes, groups } = configuration; if (groups) { for (const groupName in groups) { const objectsNames = groups[groupName]; const group = eventsFunction.getObjectGroups().insertNew(groupName, 0); for (const objectName of objectsNames) { group.addObject(objectName); } } } if (parameterTypes) { for (const parameterName in parameterTypes) { const parameterType = parameterTypes[parameterName]; const parameters = eventsFunction.getParameters(); parameters.addNewParameter(parameterName).setType(parameterType); } } } const runCompiledEvents = generateCompiledEventsForEventsFunction( gd, project, eventsFunction, { logCode: configuration && configuration.logCode, } ); eventsFunction.delete(); project.delete(); return runCompiledEvents; } /** * Helper to create compiled events from serialized events, creating a project and the events function. * @param {*} gd * @param {gdEventsFunctionExtension} extension * @param {gdSerializerElement} eventsSerializerElement * @param {{parameterTypes: {[name: string]: string}, groups: {[name: string]: string[]}, logCode: boolean}?} configuration * @returns */ function generateCompiledEventsFunctionFromSerializedEvents( gd, extension, eventsSerializerElement, configuration ) { const project = new gd.ProjectHelper.createNewGDJSProject(); const eventsFunction = new gd.EventsFunction(); eventsFunction.getEvents().unserializeFrom(project, eventsSerializerElement); if (configuration) { const { parameterTypes, groups } = configuration; if (groups) { for (const groupName in groups) { const objectsNames = groups[groupName]; const group = eventsFunction.getObjectGroups().insertNew(groupName, 0); for (const objectName of objectsNames) { group.addObject(objectName); } } } if (parameterTypes) { for (const parameterName in parameterTypes) { const parameterType = parameterTypes[parameterName]; const parameters = eventsFunction.getParameters(); parameters.addNewParameter(parameterName).setType(parameterType); } } } const runCompiledEvents = generateCompiledEventsForEventsFunctionWithContext( gd, project, extension, eventsFunction, configuration && configuration.logCode ); eventsFunction.delete(); project.delete(); return runCompiledEvents; } /** * Generate a function to run the compiled events of a layout. */ function generateCompiledEventsForLayout(gd, project, layout, logCode = false) { const includeFiles = new gd.SetString(); const layoutCodeGenerator = new gd.LayoutCodeGenerator(project); const diagnosticReport = new gd.DiagnosticReport(); const code = layoutCodeGenerator.generateLayoutCompleteCode( layout, includeFiles, diagnosticReport, true ); layoutCodeGenerator.delete(); includeFiles.delete(); if (logCode) console.log(code); // Create a function running the generated code. const compiledFunction = new Function( 'gdjs', 'runtimeScene', `"use strict"; const Hashtable = gdjs.Hashtable; ${code} return gdjs['${layout.getName()}Code'].func(runtimeScene);` ); return compiledFunction; } module.exports = { generateCompiledEventsForEventsFunction, generateCompiledEventsForEventsFunctionWithContext, generateCompiledEventsFromSerializedEvents, generateCompiledEventsFunctionFromSerializedEvents, generateCompiledEventsForSerializedEventsBasedExtension, generateCompiledEventsForEventsBasedBehavior, generateCompiledEventsForEventsBasedObject, generateCompiledEventsForLayout, };