mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-21 09:35:27 -04:00
abe44723ba
- Also fix generated code for events (and Else) not working with 'use strict' (only show in developer changelog) - Also improve the UI to not show horizontal bars for disabled events and comments
485 lines
14 KiB
JavaScript
485 lines
14 KiB
JavaScript
/**
|
|
* 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,
|
|
};
|