Compare commits

...

9 Commits

Author SHA1 Message Date
Davy Hélard
249775ee2f Remove unused code. 2025-08-09 18:46:04 +02:00
Davy Hélard
6bcc810489 Fix async task clearing. 2025-08-09 18:40:12 +02:00
Davy Hélard
2a763f18dd Clear the context. 2025-08-09 18:26:55 +02:00
Davy Hélard
b401a4cb08 Avoid to allocate a new context for behaviors and objects 2025-08-09 18:26:55 +02:00
Davy Hélard
b2b0540f93 Fix a regression on ActionWithOperator 2025-08-09 18:26:55 +02:00
Davy Hélard
be59a629f0 Format 2025-08-09 18:26:54 +02:00
Davy Hélard
a40b36d52e Fix tests 2025-08-09 18:26:54 +02:00
Davy Hélard
cc12ab662d Avoid to allocate a new context 2025-08-09 18:26:54 +02:00
Davy Hélard
0b8d6986ff Generate classes for event function contexts 2025-08-09 18:26:53 +02:00
10 changed files with 431 additions and 176 deletions

View File

@@ -235,6 +235,9 @@ class GD_CORE_API EventsFunction {
/**
* \brief Return the parameters of the function that are used in the events.
*
* \warning `ActionWithOperator` function are muted by this function. Make sure
* to use the right functions container to avoid strange side effects.
*
* \note During code/extension generation, new parameters are added
* to the generated function, like "runtimeScene" and "eventsFunctionContext".

View File

@@ -40,6 +40,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
gdjs::EventsCodeGenerator& codeGenerator,
gd::String fullyQualifiedFunctionName,
gd::String functionArgumentsCode,
gd::String contextClassCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
@@ -81,6 +82,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
globalDeclarations +
globalObjectLists + "\n\n" +
codeGenerator.GetCustomCodeOutsideMain() + "\n\n" +
contextClassCode + "\n\n" +
fullyQualifiedFunctionName + " = function(" +
functionArgumentsCode +
") {\n" +
@@ -112,6 +114,7 @@ gd::String EventsCodeGenerator::GenerateLayoutCode(
codeGenerator,
codeGenerator.GetCodeNamespaceAccessor() + "func",
"runtimeScene",
"",
"runtimeScene.getOnceTriggers().startNewFrame();\n",
scene.GetEvents(),
"",
@@ -145,16 +148,40 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
auto parameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsFunctionsExtension.GetEventsFunctions()),
0, true),
0, true);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
parameterList,
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(), "",
"runtimeScene.getOnceTriggers()") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
parameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
parameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + parameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n",
eventsFunction.GetEvents(),
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -201,44 +228,59 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
auto contextParameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedBehavior.GetEventsFunctions()),
2, true) + ", that";
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
preludeCode + "\n" + "const that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the behavior.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
// By convention of Behavior Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
"var thisObjectList = [this.owner];\n" +
"var Object = Hashtable.newFrom({Object: thisObjectList});\n" +
// By convention of Behavior Events Function, the behavior is accessible
// as a parameter called "Behavior".
"var Behavior = this.name;\n" +
codeGenerator.GenerateBehaviorEventsFunctionContext(
eventsFunctionsExtension,
eventsBasedBehavior,
eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object",
"Behavior");
// TODO: this should be renamed to "instanceContainer" and have the code
// generation adapted for this (rely less on `gdjs.RuntimeScene`, and more
// on `RuntimeInstanceContainer`).
"const runtimeScene = this._runtimeScene;\n" +
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + contextParameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n";
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
auto parameterList =
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedBehavior.GetEventsFunctions()),
2,
false),
2, false);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, fullyQualifiedFunctionName, parameterList,
codeGenerator.GenerateBehaviorEventsFunctionContext(
eventsFunctionsExtension, eventsBasedBehavior, eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object", "Behavior") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
fullPreludeCode,
eventsFunction.GetEvents(),
"",
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) {" +
codeGenerator.GetCodeNamespaceAccessor() + "context.clear();\n" +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n"
"};\n",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -286,53 +328,56 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
auto contextParameterList = codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsBasedObject.GetEventsFunctions()),
1, true) + ", that";
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
preludeCode + "\n" + "const that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the object.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
"var thisObjectList = [this];\n" +
"var Object = Hashtable.newFrom({Object: thisObjectList});\n";
"const runtimeScene = this._instanceContainer;\n"
"if (!" + codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "context = new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ");\n"
"const eventsFunctionContext = " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted ? new " +
codeGenerator.GetCodeNamespaceAccessor() + "Context(" +
contextParameterList + ") : " + codeGenerator.GetCodeNamespaceAccessor() +
"context;\n" + //
"if (!" + codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted) {\n" + codeGenerator.GetCodeNamespaceAccessor() +
"context.reinitialize(" + contextParameterList + ");\n" + //
codeGenerator.GetCodeNamespaceAccessor() +
"isExecuted = true;\n"
"}\n";
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
fullPreludeCode += "var this" + childName +
"List = [...runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) +
")];\n" + "var " + childName + " = Hashtable.newFrom({" +
ConvertToStringExplicit(childObject->GetName()) +
": this" + childName + "List});\n";
}
fullPreludeCode += codeGenerator.GenerateObjectEventsFunctionContext(
eventsFunctionsExtension,
eventsBasedObject,
eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
auto parameterList =
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
// TODO EBO use constants for firstParameterIndex
eventsFunction.GetParametersForEvents(
eventsBasedObject.GetEventsFunctions()),
1,
false),
fullPreludeCode,
eventsFunction.GetEvents(),
1, false);
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, fullyQualifiedFunctionName, parameterList,
codeGenerator.GenerateObjectEventsFunctionContext(
eventsFunctionsExtension, eventsBasedObject, eventsFunction,
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object") +
codeGenerator.GetCodeNamespaceAccessor() + "context = null;\n" + //
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n",
fullPreludeCode, eventsFunction.GetEvents(),
"if (eventsFunctionContext === " +
codeGenerator.GetCodeNamespaceAccessor() + "context) " +
codeGenerator.GetCodeNamespaceAccessor() + "isExecuted = false;\n" +
endingCode,
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
@@ -376,6 +421,33 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionParameterDeclarationsList(
return declaration;
}
gd::String EventsCodeGenerator::GenerateEventsFunctionParametersToAttribues(
const gd::ParameterMetadataContainer &parameters, int firstParameterIndex,
bool addsSceneParameter) {
gd::String declaration =
addsSceneParameter ? " this.runtimeScene = runtimeScene;\n" : "";
// By convention, the first two arguments of a behavior events function
// are the object and the behavior, which are not passed to the called
// function in the generated JS code.
for (size_t i = firstParameterIndex; i < parameters.GetParametersCount(); ++i) {
const auto &parameter = parameters.GetParameter(i);
if (parameter.GetValueTypeMetadata().IsObject() ||
parameter.GetValueTypeMetadata().IsBehavior()) {
continue;
}
gd::String parameterMangledName =
parameter.GetName().empty()
? "_"
: EventsCodeNameMangler::GetMangledName(parameter.GetName());
declaration +=
" this." + parameterMangledName + " = " + parameterMangledName + ";\n";
}
declaration +=
" this.parentEventsFunctionContext = parentEventsFunctionContext;\n";
return declaration;
}
gd::String EventsCodeGenerator::GenerateFreeEventsFunctionContext(
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsFunction& eventsFunction,
@@ -410,17 +482,17 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
objectsGettersMap += " " +
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName;
objectArraysMap += " " +
ConvertToStringExplicit(thisObjectName) + ": thisObjectList";
}
if (!thisBehaviorName.empty()) {
// If we have a behavior considered as the current behavior ("this")
// (usually called Behavior in behavior events function), generate a
// slightly more optimized getter for it.
behaviorNamesMap += ConvertToStringExplicit(thisBehaviorName) + ": " +
behaviorNamesMap += " " + ConvertToStringExplicit(thisBehaviorName) + ": " +
thisBehaviorName + "\n";
// Add required behaviors from properties
@@ -437,11 +509,40 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
gd::String comma = behaviorNamesMap.empty() ? "" : ", ";
behaviorNamesMap +=
comma + ConvertToStringExplicit(propertyDescriptor.GetName()) +
": this._get" + propertyDescriptor.GetName() + "()\n";
": that._get" + propertyDescriptor.GetName() + "()\n";
}
}
}
gd::String constructorAdditionalCode =
"this.that = that;\n"
" const thisObjectList = [that.owner];\n";
if (!thisObjectName.empty()) {
constructorAdditionalCode += " const " + thisObjectName +
" = Hashtable.newFrom({ " + thisObjectName +
": thisObjectList });\n";
}
if (!thisBehaviorName.empty()) {
constructorAdditionalCode +=
" const " + thisBehaviorName + " = that.name;\n";
}
gd::String reinitializeAdditionalCode = "";
gd::String clearAdditionalCode = "";
if (!thisObjectName.empty()) {
reinitializeAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = that.owner;\n";
clearAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = null;\n";
}
if (!thisBehaviorName.empty()) {
reinitializeAdditionalCode += " this._behaviorNamesMap[" +
ConvertToStringExplicit(thisBehaviorName) +
"] = that.name;\n";
}
return GenerateEventsFunctionContext(eventsFunctionsExtension,
eventsBasedBehavior.GetEventsFunctions(),
eventsFunction,
@@ -449,6 +550,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
constructorAdditionalCode,
reinitializeAdditionalCode,
clearAdditionalCode,
thisObjectName,
thisBehaviorName);
}
@@ -470,24 +574,69 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext(
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
objectsGettersMap += " " +
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName;
objectArraysMap += " " +
ConvertToStringExplicit(thisObjectName) + ": thisObjectList";
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
const auto& childName = ManObjListName(childObject->GetName());
// child-object are never picked because they are not parameters.
objectsGettersMap += ", " +
objectsGettersMap += ",\n " +
ConvertToStringExplicit(childObject->GetName()) +
": " + childName + "\n";
objectArraysMap += ", " +
": " + childName;
objectArraysMap += ",\n " +
ConvertToStringExplicit(childObject->GetName()) +
": this" + childName + "List\n";
": this" + childName + "List";
}
}
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
gd::String constructorAdditionalCode = "this.that = that;\n"
" const thisObjectList = [that];\n";
if (!thisObjectName.empty()) {
constructorAdditionalCode += " const " + thisObjectName +
" = Hashtable.newFrom({" + thisObjectName +
": thisObjectList});\n";
}
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
constructorAdditionalCode +=
" const this" + childName + "List = [...runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) + ")];\n" + //
" const " + childName + " = Hashtable.newFrom({" +
ConvertToStringExplicit(childObject->GetName()) + ": this" + childName +
"List});\n";
}
gd::String reinitializeAdditionalCode = "";
gd::String clearAdditionalCode = "";
if (!thisObjectName.empty()) {
reinitializeAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = that;\n";
clearAdditionalCode += " this._objectArraysMap[" +
ConvertToStringExplicit(thisObjectName) +
"][0] = null;\n";
}
// Add child-objects
for (auto& childObject : eventsBasedObject.GetObjects().GetObjects()) {
// child-object are never picked because they are not parameters.
const auto& childName = ManObjListName(childObject->GetName());
reinitializeAdditionalCode +=
" gdjs.copyArray(runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) +
"), "
"this._objectArraysMap[" +
ConvertToStringExplicit(childObject->GetName()) + "]);\n";
}
return GenerateEventsFunctionContext(eventsFunctionsExtension,
eventsBasedObject.GetEventsFunctions(),
eventsFunction,
@@ -495,6 +644,9 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext(
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
constructorAdditionalCode,
reinitializeAdditionalCode,
clearAdditionalCode,
thisObjectName);
}
@@ -506,6 +658,9 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
gd::String& objectsGettersMap,
gd::String& objectArraysMap,
gd::String& behaviorNamesMap,
const gd::String& constructorAdditionalCode,
const gd::String& reinitializeAdditionalCode,
const gd::String& clearAdditionalCode,
const gd::String& thisObjectName,
const gd::String& thisBehaviorName) {
const auto& extensionName = eventsFunctionsExtension.GetName();
@@ -526,6 +681,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Conditions/expressions are available to deal with them in events.
gd::String argumentsGetters;
gd::String reinitializeObjectsMap;
gd::String reinitializeArraysMap;
gd::String reinitializeBehaviorNamesMap;
gd::String clearObjectsMap;
gd::String clearArraysMap;
for (const auto& parameterPtr : parameters.GetInternalVector()) {
const auto& parameter = *parameterPtr;
@@ -541,13 +701,26 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Generate map that will be used to get the lists of objects passed
// as parameters (either as objects lists or array).
gd::String comma = objectsGettersMap.empty() ? "" : ", ";
gd::String comma = objectsGettersMap.empty() ? "" : ",\n ";
objectsGettersMap += comma +
ConvertToStringExplicit(parameter.GetName()) + ": " +
parameterMangledName + "\n";
parameterMangledName;
objectArraysMap += comma + ConvertToStringExplicit(parameter.GetName()) +
": gdjs.objectsListsToArray(" + parameterMangledName +
")\n";
")";
reinitializeObjectsMap += " this._objectsMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = " + parameterMangledName + ";\n";
reinitializeArraysMap +=
" gdjs.objectsListsToArray(" + parameterMangledName +
", this._objectArraysMap[" +
ConvertToStringExplicit(parameter.GetName()) + "]);\n";
clearObjectsMap += " this._objectsMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = null;\n";
clearArraysMap += " this._objectArraysMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"].length = 0;\n";
} else if (gd::ParameterMetadata::IsBehavior(parameter.GetType())) {
if (parameter.GetName() == thisBehaviorName) {
continue;
@@ -555,114 +728,171 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// Generate map that will be used to transform from behavior name used in
// function to the "real" behavior name from the caller.
gd::String comma = behaviorNamesMap.empty() ? "" : ", ";
gd::String comma = behaviorNamesMap.empty() ? "" : ",\n";
behaviorNamesMap += comma + ConvertToStringExplicit(parameter.GetName()) +
": " + parameterMangledName + "\n";
": " + parameterMangledName;
reinitializeBehaviorNamesMap +=
"this._behaviorNamesMap[" +
ConvertToStringExplicit(parameter.GetName()) +
"] = " + parameterMangledName + ";\n";
} else {
argumentsGetters +=
"if (argName === " + ConvertToStringExplicit(parameter.GetName()) +
") return " + parameterMangledName + ";\n";
" if (argName === " + ConvertToStringExplicit(parameter.GetName()) +
") return this." + parameterMangledName + ";\n";
}
}
const gd::String async = eventsFunction.IsAsync()
? " task: new gdjs.ManuallyResolvableTask(),\n"
? " this.task = new gdjs.ManuallyResolvableTask(),\n"
: "";
const gd::String clearAsync = eventsFunction.IsAsync()
? " this.task = null,\n"
: "";
const int firstParameterIndex =
(thisObjectName.empty() ? 0 : 1) + (thisBehaviorName.empty() ? 0 : 1);
auto parameterList =
GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(eventsFunctionsContainer),
firstParameterIndex, true) +
(thisObjectName.empty() && thisBehaviorName.empty() ? "" : ", that");
return gd::String("var eventsFunctionContext = {\n") +
// The async task, if there is one
async +
return GetCodeNamespaceAccessor() + "Context = class {\n"
"constructor(" + parameterList + ") {\n" +
GenerateEventsFunctionParametersToAttribues(
eventsFunction.GetParametersForEvents(
eventsFunctionsContainer),
firstParameterIndex, true) +
constructorAdditionalCode +
// The async task, if there is one
async +
// The object name to parameter map:
" _objectsMap: {\n" + objectsGettersMap +
"},\n"
" this._objectsMap = {\n" +
objectsGettersMap + "\n"
" };\n"
// The object name to arrays map:
" _objectArraysMap: {\n" +
objectArraysMap +
"},\n"
" this._objectArraysMap = {\n" +
objectArraysMap + "\n"
" };\n"
// The behavior name to parameter map:
" _behaviorNamesMap: {\n" +
behaviorNamesMap +
"},\n"
" globalVariablesForExtension: "
" this._behaviorNamesMap = {\n" +
behaviorNamesMap + "\n"
" };\n"
" this.globalVariablesForExtension = "
"runtimeScene.getGame().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + "),\n" +
" sceneVariablesForExtension: "
ConvertToStringExplicit(extensionName) + ");\n" +
" this.sceneVariablesForExtension = "
"runtimeScene.getScene().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + "),\n" +
ConvertToStringExplicit(extensionName) + ");\n"
// The local variables stack:
" localVariables: [],\n"
" this.localVariables = [];\n"
"}\n"
"reinitialize(" + parameterList + ") {\n" +
GenerateEventsFunctionParametersToAttribues(
eventsFunction.GetParametersForEvents(
eventsFunctionsContainer),
firstParameterIndex, true) +
// The async task, if there is one
async +
// The object name to parameter map:
reinitializeObjectsMap +
// The object name to arrays map:
reinitializeArraysMap +
// The behavior name to parameter map:
reinitializeBehaviorNamesMap +
// globalVariablesForExtension stays the same.
" this.sceneVariablesForExtension = "
"runtimeScene.getScene().getVariablesForExtension(" +
ConvertToStringExplicit(extensionName) + ");\n" +
reinitializeAdditionalCode +
"}\n"
"clear() {\n" +
" this.runtimeScene = null;\n"
" this.parentEventsFunctionContext = null;\n"
// globalVariablesForExtension stays the same.
" this.sceneVariablesForExtension = null;\n" +
// The async task, if there is one
clearAsync +
// The object name to parameter map:
clearObjectsMap +
// The object name to arrays map:
clearArraysMap +
clearAdditionalCode +
"}\n"
// Function that will be used to query objects, when a new object list
// is needed by events. We assume it's used a lot by the events
// generated code, so we cache the arrays in a map.
" getObjects: function(objectName) {\n" +
" return eventsFunctionContext._objectArraysMap[objectName] || "
"[];\n" +
" },\n" +
" getObjects(objectName) {\n"
" return this._objectArraysMap[objectName] || [];\n"
" }\n"
// Function that can be used in JS code to get the lists of objects
// and filter/alter them (not actually used in events).
" getObjectsLists: function(objectName) {\n" +
" return eventsFunctionContext._objectsMap[objectName] || null;\n"
" },\n" +
" getObjectsLists(objectName) {\n"
" return this._objectsMap[objectName] || null;\n"
" }\n"
// Function that will be used to query behavior name (as behavior name
// can be different between the parameter name vs the actual behavior
// name passed as argument).
" getBehaviorName: function(behaviorName) {\n" +
" getBehaviorName(behaviorName) {\n"
// TODO EBO Handle behavior name collision between parameters and
// children
" return eventsFunctionContext._behaviorNamesMap[behaviorName] || "
" return this._behaviorNamesMap[behaviorName] || "
"behaviorName;\n"
" },\n" +
" }\n"
// Creator function that will be used to create new objects. We
// need to check if the function was given the context of the calling
// function (parentEventsFunctionContext). If this is the case, use it
// to create the new object as the object names used in the function
// are not the same as the objects available in the scene.
" createObject: function(objectName) {\n"
" const objectsList = "
"eventsFunctionContext._objectsMap[objectName];\n" +
" createObject(objectName) {\n"
" const objectsList = this._objectsMap[objectName];\n"
// TODO: we could speed this up by storing a map of object names, but
// the cost of creating/storing it for each events function might not
// be worth it.
" if (objectsList) {\n" +
" const object = parentEventsFunctionContext ?\n" +
" if (objectsList) {\n"
" const object = this.parentEventsFunctionContext ?\n"
" "
"parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n" +
" runtimeScene.createObject(objectsList.firstKey());\n" +
"this.parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n"
" this.runtimeScene.createObject(objectsList.firstKey());\n"
// Add the new instance to object lists
" if (object) {\n" +
" objectsList.get(objectsList.firstKey()).push(object);\n" +
" "
"eventsFunctionContext._objectArraysMap[objectName].push(object);\n" +
" }\n" + " return object;" + " }\n" +
" if (object) {\n"
" objectsList.get(objectsList.firstKey()).push(object);\n"
" this._objectArraysMap[objectName].push(object);\n"
" }\n"
" return object;\n"
" }\n"
// Unknown object, don't create anything:
" return null;\n" +
" },\n"
" return null;\n"
" }\n"
// Function to count instances on the scene. We need it here because
// it needs the objects map to get the object names of the parent
// context.
" getInstancesCountOnScene: function(objectName) {\n"
" const objectsList = "
"eventsFunctionContext._objectsMap[objectName];\n" +
" let count = 0;\n" + " if (objectsList) {\n" +
" for(const objectName in objectsList.items)\n" +
" count += parentEventsFunctionContext ?\n" +
"parentEventsFunctionContext.getInstancesCountOnScene(objectName) "
":\n" +
" runtimeScene.getInstancesCountOnScene(objectName);\n" +
" }\n" + " return count;\n" +
" },\n"
" getInstancesCountOnScene(objectName) {\n"
" const objectsList = this._objectsMap[objectName];\n"
" let count = 0;\n"
" if (objectsList) {\n"
" for(const objectName in objectsList.items)\n"
" count += this.parentEventsFunctionContext ?\n"
" this.parentEventsFunctionContext.getInstancesCountOnScene(objectName) :\n"
" this.runtimeScene.getInstancesCountOnScene(objectName);\n"
" }\n"
" return count;\n"
" }\n"
// Allow to get a layer directly from the context for convenience:
" getLayer: function(layerName) {\n"
" return runtimeScene.getLayer(layerName);\n"
" },\n"
" getLayer(layerName) {\n"
" return this.runtimeScene.getLayer(layerName);\n"
" }\n"
// Getter for arguments that are not objects
" getArgument: function(argName) {\n" +
argumentsGetters + " return \"\";\n" + " },\n" +
" getArgument(argName) {\n" + argumentsGetters + //
" return \"\";\n"
" }\n"
// Expose OnceTriggers (will be pointing either to the runtime scene
// ones, or the ones from the behavior):
" getOnceTriggers: function() { return " + onceTriggersVariable +
"; }\n" + "};\n";
" getOnceTriggers() { return this." + onceTriggersVariable + "; }\n"
"}\n";
}
gd::String EventsCodeGenerator::GenerateEventsFunctionReturn(

View File

@@ -375,6 +375,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gdjs::EventsCodeGenerator& codeGenerator,
gd::String fullyQualifiedFunctionName,
gd::String functionArgumentsCode,
gd::String contextClassCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
@@ -409,6 +410,16 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
int firstParameterIndex,
bool addsSceneParameter);
/**
* \brief Generate the affectation from parameters to class attributes.
*
* \note runtimeScene is always added as the first parameter, and
* parentEventsFunctionContext as the last parameter.
*/
gd::String GenerateEventsFunctionParametersToAttribues(
const gd::ParameterMetadataContainer &parameters, int firstParameterIndex,
bool addsSceneParameter);
/**
* \brief Generate the "eventsFunctionContext" object that allow a free
* function to provides access objects, object creation and access to
@@ -475,6 +486,9 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gd::String &objectsGettersMap,
gd::String &objectArraysMap,
gd::String &behaviorNamesMap,
const gd::String &constructorAdditionalCode = "",
const gd::String &reinitializeAdditionalCode = "",
const gd::String& clearAdditionalCode = "",
const gd::String &thisObjectName = "",
const gd::String &thisBehaviorName = "");
};

View File

@@ -502,18 +502,21 @@ namespace gdjs {
* @returns {Array}
*/
export const objectsListsToArray = function (
objectsLists: Hashtable<RuntimeObject>
objectsLists: Hashtable<RuntimeObject>,
result: Array<RuntimeObject> = []
): Array<RuntimeObject> {
var lists = gdjs.staticArray(gdjs.objectsListsToArray);
const lists = gdjs.staticArray(gdjs.objectsListsToArray);
objectsLists.values(lists);
var result: Array<RuntimeObject> = [];
for (var i = 0; i < lists.length; ++i) {
var arr = lists[i];
for (var k = 0; k < arr.length; ++k) {
result.push(arr[k]);
let resultIndex = 0;
for (let i = 0; i < lists.length; ++i) {
const arr = lists[i];
for (let k = 0; k < arr.length; ++k) {
result[resultIndex] = arr[k];
resultIndex++;
}
}
result.length = resultIndex;
return result;
};
@@ -524,8 +527,8 @@ namespace gdjs {
* @param dst The destination array
*/
export const copyArray = function <T>(src: Array<T>, dst: Array<T>): void {
var len = src.length;
for (var i = 0; i < len; ++i) {
const len = src.length;
for (let i = 0; i < len; ++i) {
dst[i] = src[i];
}
dst.length = len;

View File

@@ -239,7 +239,8 @@ function generateCompiledEventsForSerializedEventsBasedExtension(
gd,
serializedEventsFunctionsExtension,
gdjs,
runtimeScene
runtimeScene,
options
) {
const project = new gd.ProjectHelper.createNewGDJSProject();
const extension = project.insertNewEventsFunctionsExtension(
@@ -294,7 +295,8 @@ function generateCompiledEventsForSerializedEventsBasedExtension(
project,
extension,
behavior,
gdjs
gdjs,
options
);
}

View File

@@ -531,7 +531,7 @@ describe('libGD.js - GDJS related tests', function () {
// Check that the context for the events function is here...
expect(code).toMatch('function(runtimeScene, eventsFunctionContext)');
expect(code).toMatch('var eventsFunctionContext =');
expect(code).toMatch('const eventsFunctionContext =');
// Check that the parameters, with the (optional) context of the parent function,
// are all here
@@ -544,8 +544,8 @@ describe('libGD.js - GDJS related tests', function () {
expect(code).toMatch('"MySprite": MySprite');
// ...and arguments should be able to get queried too:
expect(code).toMatch('if (argName === "MyNumber") return MyNumber;');
expect(code).toMatch('if (argName === "MyString") return MyString;');
expect(code).toMatch('if (argName === "MyNumber") return this.MyNumber;');
expect(code).toMatch('if (argName === "MyString") return this.MyString;');
// GetArgumentAsString("MyString") should be generated code to query and cast as a string
// the argument
@@ -635,7 +635,7 @@ describe('libGD.js - GDJS related tests', function () {
// Check that the context for the events function is here...
expect(code).toMatch('function(runtimeScene, eventsFunctionContext)');
expect(code).toMatch('var eventsFunctionContext =');
expect(code).toMatch('const eventsFunctionContext =');
// Check that the parameters, with the (optional) context of the parent function,
// are all here

View File

@@ -80,7 +80,7 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function
eventsFunctionsExtension,
eventsBasedBehavior,
gdjs,
{logCode: false}
{ logCode: false }
);
project.delete();

View File

@@ -3578,7 +3578,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
gd,
require('./extensions/EBAsyncAction.json'),
gdjs,
runtimeScene
runtimeScene,
{ logCode: true }
);
const {
@@ -3618,7 +3619,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
gd,
require('./extensions/EBAsyncAction.json'),
gdjs,
runtimeScene
runtimeScene,
{ logCode: false }
);
const {

View File

@@ -12,7 +12,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
});
describe('SceneInstancesCount', () => {
const prepareCompiledEvents = () => {
const prepareCompiledEvents = (options = {}) => {
const eventsSerializerElement = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
@@ -62,7 +62,8 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
const runCompiledEvents = generateCompiledEventsForEventsFunction(
gd,
project,
eventsFunction
eventsFunction,
options.logCode
);
eventsFunction.delete();
@@ -71,7 +72,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
};
it('counts instances from the scene in a function, when no instances are passed as parameters', () => {
const { runCompiledEvents } = prepareCompiledEvents();
const { runCompiledEvents } = prepareCompiledEvents({ logCode: false });
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
runtimeScene.getOnceTriggers().startNewFrame();
@@ -106,7 +107,7 @@ describe('libGD.js - GDJS Object Code Generation integration tests', function ()
});
it('counts instances from the scene in a function, when some instances are passed as parameters', () => {
const { runCompiledEvents } = prepareCompiledEvents();
const { runCompiledEvents } = prepareCompiledEvents({ logCode: false });
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
runtimeScene.getOnceTriggers().startNewFrame();

View File

@@ -737,7 +737,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
@@ -787,7 +787,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);
@@ -882,7 +882,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
},
],
{
logCode: true,
logCode: false,
}
);
expect(runtimeScene.getVariables().has('Counter')).toBe(true);