From 6497a28df05d65f313990628c37a53cc51999c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 2 Nov 2024 18:15:05 +0100 Subject: [PATCH 01/26] Expose parameters and properties in VariablesContainerList. --- .../Metadata/ParameterMetadataTools.cpp | 5 +- Core/GDCore/IDE/EventsFunctionTools.cpp | 73 +++++++++++++- Core/GDCore/IDE/EventsFunctionTools.h | 11 +++ Core/GDCore/IDE/ProjectBrowserHelper.cpp | 20 +++- .../Project/ProjectScopedContainers.cpp | 30 ++++-- Core/GDCore/Project/ProjectScopedContainers.h | 13 ++- Core/GDCore/Project/VariablesContainer.h | 4 +- .../Project/VariablesContainersList.cpp | 67 +++++++++++++ Core/GDCore/Project/VariablesContainersList.h | 97 +++++++++++++------ .../CodeGeneration/EventsCodeGenerator.cpp | 18 +++- GDevelop.js/Bindings/Bindings.idl | 16 ++- GDevelop.js/scripts/generate-types.js | 4 +- GDevelop.js/types.d.ts | 9 +- .../types/gdprojectscopedcontainers.js | 6 +- GDevelop.js/types/gdvariablescontainer.js | 3 +- .../types/variablescontainer_sourcetype.js | 2 +- .../EventsFunctionsExtensionEditor/index.js | 10 +- .../ParameterFields/AnyVariableField.js | 8 ++ .../ParameterFields/EnumerateVariables.js | 24 ++++- .../EnumerateVariables.spec.js | 4 +- .../ParameterFields/VariableField.js | 6 ++ .../InstructionOrExpression/EventsScope.js | 34 ++++++- .../VariableToTreeNodeHandling.spec.js | 4 +- 23 files changed, 387 insertions(+), 81 deletions(-) diff --git a/Core/GDCore/Extensions/Metadata/ParameterMetadataTools.cpp b/Core/GDCore/Extensions/Metadata/ParameterMetadataTools.cpp index d4e949c4b4ae..8881fe93b46e 100644 --- a/Core/GDCore/Extensions/Metadata/ParameterMetadataTools.cpp +++ b/Core/GDCore/Extensions/Metadata/ParameterMetadataTools.cpp @@ -33,7 +33,8 @@ void ParameterMetadataTools::ParametersToObjectsContainer( const auto& parameter = parameters.GetParameter(i); if (parameter.GetName().empty()) continue; - if (gd::ParameterMetadata::IsObject(parameter.GetType())) { + auto &valueTypeMetadata = parameter.GetValueTypeMetadata(); + if (valueTypeMetadata.IsObject()) { const gd::String& objectName = parameter.GetName(); const gd::String& objectType = parameter.GetExtraInfo(); allObjectNames.insert(objectName); @@ -68,7 +69,7 @@ void ParameterMetadataTools::ParametersToObjectsContainer( // Search "lastObjectName" in the codebase for other place where this // convention is enforced. lastObjectName = objectName; - } else if (gd::ParameterMetadata::IsBehavior(parameter.GetType())) { + } else if (valueTypeMetadata.IsBehavior()) { if (!lastObjectName.empty()) { if (outputObjectsContainer.HasObjectNamed(lastObjectName)) { const gd::String& behaviorName = parameter.GetName(); diff --git a/Core/GDCore/IDE/EventsFunctionTools.cpp b/Core/GDCore/IDE/EventsFunctionTools.cpp index a92e02a148b5..1e475e91fc5e 100644 --- a/Core/GDCore/IDE/EventsFunctionTools.cpp +++ b/Core/GDCore/IDE/EventsFunctionTools.cpp @@ -9,7 +9,10 @@ #include "GDCore/Extensions/Metadata/ParameterMetadataTools.h" #include "GDCore/Project/EventsBasedBehavior.h" #include "GDCore/Project/EventsBasedObject.h" -//#include "GDCore/Project/ObjectsContainer.h" +#include "GDCore/Project/ObjectsContainer.h" +#include "GDCore/Project/ParameterMetadataContainer.h" +#include "GDCore/Project/PropertiesContainer.h" +#include "GDCore/Project/VariablesContainer.h" #include "GDCore/Project/EventsFunction.h" #include "GDCore/Project/Object.h" #include "GDCore/Project/Project.h" @@ -100,4 +103,72 @@ void EventsFunctionTools::ObjectEventsFunctionToObjectsContainer( } } +void EventsFunctionTools::ParametersToVariablesContainer( + const ParameterMetadataContainer ¶meters, + gd::VariablesContainer &outputVariablesContainer) { + if (outputVariablesContainer.GetSourceType() != + gd::VariablesContainer::SourceType::Parameters) { + throw std::logic_error("Tried to generate a variables container from " + "parameters with the wrong source type."); + } + outputVariablesContainer.Clear(); + + gd::String lastObjectName; + for (std::size_t i = 0; i < parameters.GetParametersCount(); ++i) { + const auto ¶meter = parameters.GetParameter(i); + if (parameter.GetName().empty()) + continue; + + auto &valueTypeMetadata = parameter.GetValueTypeMetadata(); + if (valueTypeMetadata.IsNumber()) { + auto &variable = outputVariablesContainer.InsertNew( + parameter.GetName(), outputVariablesContainer.Count()); + variable.SetValue(0); + } else if (valueTypeMetadata.IsString()) { + auto &variable = outputVariablesContainer.InsertNew( + parameter.GetName(), outputVariablesContainer.Count()); + variable.SetString(""); + } else if (valueTypeMetadata.IsBoolean()) { + auto &variable = outputVariablesContainer.InsertNew( + parameter.GetName(), outputVariablesContainer.Count()); + variable.SetBool(false); + } + } +} + +void EventsFunctionTools::PropertiesToVariablesContainer( + const PropertiesContainer &properties, + gd::VariablesContainer &outputVariablesContainer) { + if (outputVariablesContainer.GetSourceType() != + gd::VariablesContainer::SourceType::Properties) { + throw std::logic_error("Tried to generate a variables container from " + "properties with the wrong source type."); + } + outputVariablesContainer.Clear(); + + gd::String lastObjectName; + for (std::size_t i = 0; i < properties.GetCount(); ++i) { + const auto &property = properties.Get(i); + if (property.GetName().empty()) + continue; + + auto &propertyType = gd::ValueTypeMetadata::GetPrimitiveValueType( + gd::ValueTypeMetadata::ConvertPropertyTypeToValueType( + property.GetType())); + if (propertyType == "number") { + auto &variable = outputVariablesContainer.InsertNew( + property.GetName(), outputVariablesContainer.Count()); + variable.SetValue(0); + } else if (propertyType == "string") { + auto &variable = outputVariablesContainer.InsertNew( + property.GetName(), outputVariablesContainer.Count()); + variable.SetString(""); + } else if (propertyType == "boolean") { + auto &variable = outputVariablesContainer.InsertNew( + property.GetName(), outputVariablesContainer.Count()); + variable.SetBool(false); + } + } +} + } // namespace gd diff --git a/Core/GDCore/IDE/EventsFunctionTools.h b/Core/GDCore/IDE/EventsFunctionTools.h index 3ee995227998..ddbd3a7848d0 100644 --- a/Core/GDCore/IDE/EventsFunctionTools.h +++ b/Core/GDCore/IDE/EventsFunctionTools.h @@ -12,6 +12,9 @@ namespace gd { class Project; class EventsFunctionsContainer; class ObjectsContainer; +class ParameterMetadataContainer; +class PropertiesContainer; +class VariablesContainer; class ParameterMetadata; class EventsFunction; class EventsBasedBehavior; @@ -68,5 +71,13 @@ class GD_CORE_API EventsFunctionTools { const gd::EventsBasedObject& eventsBasedObject, const gd::EventsFunction& eventsFunction, gd::ObjectsContainer& outputObjectsContainer); + + static void ParametersToVariablesContainer( + const ParameterMetadataContainer ¶meters, + gd::VariablesContainer &outputVariablesContainer); + + static void PropertiesToVariablesContainer( + const PropertiesContainer &properties, + gd::VariablesContainer &outputVariablesContainer); }; } // namespace gd diff --git a/Core/GDCore/IDE/ProjectBrowserHelper.cpp b/Core/GDCore/IDE/ProjectBrowserHelper.cpp index 36e0098ff94a..d13f96e59aae 100644 --- a/Core/GDCore/IDE/ProjectBrowserHelper.cpp +++ b/Core/GDCore/IDE/ProjectBrowserHelper.cpp @@ -172,10 +172,14 @@ void ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents( for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) { gd::ObjectsContainer parameterObjectsContainer( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForFreeEventsFunction( project, eventsFunctionsExtension, *eventsFunction, - parameterObjectsContainer); + parameterObjectsContainer, parameterVariablesContainer); worker.Launch(eventsFunction->GetEvents(), projectScopedContainers); } @@ -212,10 +216,15 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( gd::ObjectsContainer parameterObjectsContainers( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForBehaviorEventsFunction( project, eventsFunctionsExtension, eventsBasedBehavior, - *eventsFunction, parameterObjectsContainers); + *eventsFunction, parameterObjectsContainers, + parameterVariablesContainer, propertyVariablesContainer); worker.Launch(eventsFunction->GetEvents(), projectScopedContainers); } @@ -240,10 +249,15 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents( gd::ObjectsContainer parameterObjectsContainers( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForObjectEventsFunction( project, eventsFunctionsExtension, eventsBasedObject, - *eventsFunction, parameterObjectsContainers); + *eventsFunction, parameterObjectsContainers, + parameterVariablesContainer, propertyVariablesContainer); worker.Launch(eventsFunction->GetEvents(), projectScopedContainers); } diff --git a/Core/GDCore/Project/ProjectScopedContainers.cpp b/Core/GDCore/Project/ProjectScopedContainers.cpp index 1559862a2919..4aa66b08aa3c 100644 --- a/Core/GDCore/Project/ProjectScopedContainers.cpp +++ b/Core/GDCore/Project/ProjectScopedContainers.cpp @@ -70,18 +70,23 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForEventsFunctionsExtensi ProjectScopedContainers ProjectScopedContainers::MakeNewProjectScopedContainersForFreeEventsFunction( - const gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsFunction &eventsFunction, - gd::ObjectsContainer ¶meterObjectsContainer) { + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer) { gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer( - project, eventsFunctionsExtension, eventsFunction, parameterObjectsContainer); + project, eventsFunctionsExtension, eventsFunction, + parameterObjectsContainer); ProjectScopedContainers projectScopedContainers( ObjectsContainersList::MakeNewObjectsContainersListForContainer( parameterObjectsContainer), VariablesContainersList:: - MakeNewVariablesContainersListForEventsFunctionsExtension(eventsFunctionsExtension), + MakeNewVariablesContainersListForFreeEventsFunction( + eventsFunctionsExtension, eventsFunction, + parameterVariablesContainer), &eventsFunctionsExtension.GetGlobalVariables(), &eventsFunctionsExtension.GetSceneVariables(), PropertiesContainersList::MakeNewEmptyPropertiesContainersList()); @@ -97,7 +102,9 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForBehaviorEventsFunction const gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedBehavior& eventsBasedBehavior, const gd::EventsFunction &eventsFunction, - gd::ObjectsContainer ¶meterObjectsContainer) { + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer) { gd::EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer( project, @@ -109,7 +116,9 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForBehaviorEventsFunction ObjectsContainersList::MakeNewObjectsContainersListForContainer( parameterObjectsContainer), VariablesContainersList:: - MakeNewVariablesContainersListForEventsFunctionsExtension(eventsFunctionsExtension), + MakeNewVariablesContainersListForBehaviorEventsFunction( + eventsFunctionsExtension, eventsBasedBehavior, eventsFunction, + parameterVariablesContainer, propertyVariablesContainer), &eventsFunctionsExtension.GetGlobalVariables(), &eventsFunctionsExtension.GetSceneVariables(), PropertiesContainersList::MakeNewEmptyPropertiesContainersList()); @@ -130,7 +139,9 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForObjectEventsFunction( const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedObject &eventsBasedObject, const gd::EventsFunction &eventsFunction, - gd::ObjectsContainer ¶meterObjectsContainer) { + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer) { gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer( project, eventsBasedObject, eventsFunction, parameterObjectsContainer); @@ -140,8 +151,9 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForObjectEventsFunction( eventsBasedObject.GetObjects(), parameterObjectsContainer), VariablesContainersList:: - MakeNewVariablesContainersListForEventsFunctionsExtension( - eventsFunctionsExtension), + MakeNewVariablesContainersListForObjectEventsFunction( + eventsFunctionsExtension, eventsBasedObject, eventsFunction, + parameterVariablesContainer, propertyVariablesContainer), &eventsFunctionsExtension.GetGlobalVariables(), &eventsFunctionsExtension.GetSceneVariables(), PropertiesContainersList::MakeNewEmptyPropertiesContainersList()); diff --git a/Core/GDCore/Project/ProjectScopedContainers.h b/Core/GDCore/Project/ProjectScopedContainers.h index 74d304bf188a..85e819812490 100644 --- a/Core/GDCore/Project/ProjectScopedContainers.h +++ b/Core/GDCore/Project/ProjectScopedContainers.h @@ -69,8 +69,9 @@ class ProjectScopedContainers { MakeNewProjectScopedContainersForFreeEventsFunction( const gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, - const gd::EventsFunction& eventsFunction, - gd::ObjectsContainer& parameterObjectsContainer); + const gd::EventsFunction &eventsFunction, + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer); static ProjectScopedContainers MakeNewProjectScopedContainersForBehaviorEventsFunction( @@ -78,7 +79,9 @@ class ProjectScopedContainers { const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedBehavior &eventsBasedBehavior, const gd::EventsFunction &eventsFunction, - gd::ObjectsContainer ¶meterObjectsContainer); + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer); static ProjectScopedContainers MakeNewProjectScopedContainersForObjectEventsFunction( @@ -86,7 +89,9 @@ class ProjectScopedContainers { const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedObject &eventsBasedObject, const gd::EventsFunction &eventsFunction, - gd::ObjectsContainer ¶meterObjectsContainer); + gd::ObjectsContainer ¶meterObjectsContainer, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer); static ProjectScopedContainers MakeNewProjectScopedContainersForEventsBasedObject( diff --git a/Core/GDCore/Project/VariablesContainer.h b/Core/GDCore/Project/VariablesContainer.h index 2cd526185695..f29a73b00a30 100644 --- a/Core/GDCore/Project/VariablesContainer.h +++ b/Core/GDCore/Project/VariablesContainer.h @@ -34,7 +34,9 @@ class GD_CORE_API VariablesContainer { Object, Local, ExtensionGlobal, - ExtensionScene + ExtensionScene, + Parameters, + Properties, }; VariablesContainer(); diff --git a/Core/GDCore/Project/VariablesContainersList.cpp b/Core/GDCore/Project/VariablesContainersList.cpp index b9a63773e7d3..9cac7a378f01 100644 --- a/Core/GDCore/Project/VariablesContainersList.cpp +++ b/Core/GDCore/Project/VariablesContainersList.cpp @@ -6,6 +6,7 @@ #include "GDCore/Project/Project.h" #include "GDCore/Project/Variable.h" #include "GDCore/Project/EventsFunctionsExtension.h" +#include "GDCore/IDE/EventsFunctionTools.h" namespace gd { @@ -41,6 +42,72 @@ VariablesContainersList::MakeNewVariablesContainersListForEventsFunctionsExtensi return variablesContainersList; } +VariablesContainersList +VariablesContainersList::MakeNewVariablesContainersListForFreeEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer) { + VariablesContainersList variablesContainersList; + variablesContainersList.Push(extension.GetGlobalVariables()); + variablesContainersList.Push(extension.GetSceneVariables()); + + gd::EventsFunctionTools::ParametersToVariablesContainer( + eventsFunction.GetParametersForEvents(extension), + parameterVariablesContainer); + variablesContainersList.Push(parameterVariablesContainer); + + variablesContainersList.firstLocalVariableContainerIndex = 3; + return variablesContainersList; +} + +VariablesContainersList VariablesContainersList:: + MakeNewVariablesContainersListForBehaviorEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer) { + VariablesContainersList variablesContainersList; + variablesContainersList.Push(extension.GetGlobalVariables()); + variablesContainersList.Push(extension.GetSceneVariables()); + + gd::EventsFunctionTools::ParametersToVariablesContainer( + eventsFunction.GetParametersForEvents(extension), + parameterVariablesContainer); + variablesContainersList.Push(parameterVariablesContainer); + + gd::EventsFunctionTools::PropertiesToVariablesContainer( + eventsBasedBehavior.GetPropertyDescriptors(), propertyVariablesContainer); + variablesContainersList.Push(propertyVariablesContainer); + + variablesContainersList.firstLocalVariableContainerIndex = 4; + return variablesContainersList; +} + +VariablesContainersList +VariablesContainersList::MakeNewVariablesContainersListForObjectEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsBasedObject &eventsBasedObject, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer) { + VariablesContainersList variablesContainersList; + variablesContainersList.Push(extension.GetGlobalVariables()); + variablesContainersList.Push(extension.GetSceneVariables()); + + gd::EventsFunctionTools::ParametersToVariablesContainer( + eventsFunction.GetParametersForEvents(extension), + parameterVariablesContainer); + variablesContainersList.Push(parameterVariablesContainer); + + gd::EventsFunctionTools::PropertiesToVariablesContainer( + eventsBasedObject.GetPropertyDescriptors(), propertyVariablesContainer); + variablesContainersList.Push(propertyVariablesContainer); + + variablesContainersList.firstLocalVariableContainerIndex = 4; + return variablesContainersList; +} + VariablesContainersList VariablesContainersList::MakeNewVariablesContainersListPushing( const VariablesContainersList& variablesContainersList, const gd::VariablesContainer& variablesContainer) { diff --git a/Core/GDCore/Project/VariablesContainersList.h b/Core/GDCore/Project/VariablesContainersList.h index 3eb0bd3700ae..0b481eea7b6d 100644 --- a/Core/GDCore/Project/VariablesContainersList.h +++ b/Core/GDCore/Project/VariablesContainersList.h @@ -1,6 +1,6 @@ #pragma once -#include #include +#include namespace gd { class String; @@ -9,7 +9,10 @@ class Layout; class VariablesContainer; class Variable; class EventsFunctionsExtension; -} // namespace gd +class EventsBasedBehavior; +class EventsBasedObject; +class EventsFunction; +} // namespace gd namespace gd { @@ -24,20 +27,42 @@ namespace gd { * \ingroup PlatformDefinition */ class GD_CORE_API VariablesContainersList { - public: +public: virtual ~VariablesContainersList(){}; static VariablesContainersList - MakeNewVariablesContainersListForProjectAndLayout(const gd::Project& project, - const gd::Layout& layout); + MakeNewVariablesContainersListForProjectAndLayout(const gd::Project &project, + const gd::Layout &layout); static VariablesContainersList - MakeNewVariablesContainersListForProject(const gd::Project& project); + MakeNewVariablesContainersListForProject(const gd::Project &project); static VariablesContainersList MakeNewVariablesContainersListForEventsFunctionsExtension( const gd::EventsFunctionsExtension &extension); + static VariablesContainersList + MakeNewVariablesContainersListForFreeEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer); + + static VariablesContainersList + MakeNewVariablesContainersListForBehaviorEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer); + + static VariablesContainersList + MakeNewVariablesContainersListForObjectEventsFunction( + const gd::EventsFunctionsExtension &extension, + const gd::EventsBasedObject &eventsBasedObject, + const gd::EventsFunction &eventsFunction, + gd::VariablesContainer ¶meterVariablesContainer, + gd::VariablesContainer &propertyVariablesContainer); + static VariablesContainersList MakeNewVariablesContainersListPushing( const VariablesContainersList &variablesContainersList, const gd::VariablesContainer &variablesContainer); @@ -50,28 +75,31 @@ class GD_CORE_API VariablesContainersList { /** * \brief Return true if the specified variable is in one of the containers. */ - bool Has(const gd::String& name) const; + bool Has(const gd::String &name) const; /** * \brief Return a reference to the variable called \a name. */ - const Variable& Get(const gd::String& name) const; + const Variable &Get(const gd::String &name) const; /** * \brief Return true if the specified variable container is present. */ - bool HasVariablesContainer(const gd::VariablesContainer& variablesContainer) const; + bool + HasVariablesContainer(const gd::VariablesContainer &variablesContainer) const; - // TODO: Rename GetTopMostVariablesContainer and GetBottomMostVariablesContainer - // to give a clearer access to segments of the container list. - // For instance, a project tree segment and an event tree segment. + // TODO: Rename GetTopMostVariablesContainer and + // GetBottomMostVariablesContainer to give a clearer access to segments of the + // container list. For instance, a project tree segment and an event tree + // segment. /** - * Get the variables container at the top of the scope (so the most "global" one). - * \brief Avoid using apart when a scope must be forced. + * Get the variables container at the top of the scope (so the most "global" + * one). \brief Avoid using apart when a scope must be forced. */ - const VariablesContainer* GetTopMostVariablesContainer() const { - if (variablesContainers.empty()) return nullptr; + const VariablesContainer *GetTopMostVariablesContainer() const { + if (variablesContainers.empty()) + return nullptr; return variablesContainers.front(); }; @@ -80,8 +108,9 @@ class GD_CORE_API VariablesContainersList { * (so the most "local" one) excluding local variables. * \brief Avoid using apart when a scope must be forced. */ - const VariablesContainer* GetBottomMostVariablesContainer() const { - if (variablesContainers.empty()) return nullptr; + const VariablesContainer *GetBottomMostVariablesContainer() const { + if (variablesContainers.empty()) + return nullptr; return variablesContainers.at(firstLocalVariableContainerIndex - 1); } @@ -109,43 +138,47 @@ class GD_CORE_API VariablesContainersList { * \warning Trying to access to a not existing variable container will result * in undefined behavior. */ - const gd::VariablesContainer& GetVariablesContainer(std::size_t index) const { + const gd::VariablesContainer &GetVariablesContainer(std::size_t index) const { return *variablesContainers.at(index); } /** * \brief Return the number variable containers. */ - std::size_t GetVariablesContainersCount() const { return variablesContainers.size(); } + std::size_t GetVariablesContainersCount() const { + return variablesContainers.size(); + } /** - * \brief Call the callback for each variable having a name matching the specified search. + * \brief Call the callback for each variable having a name matching the + * specified search. */ - void ForEachVariableMatchingSearch(const gd::String& search, std::function fn) const; + void ForEachVariableMatchingSearch( + const gd::String &search, + std::function + fn) const; /** * \brief Push a new variables container to the context. */ - void Push(const gd::VariablesContainer& variablesContainer) { + void Push(const gd::VariablesContainer &variablesContainer) { variablesContainers.push_back(&variablesContainer); }; /** * \brief Pop a variables container from the context. */ - void Pop() { - variablesContainers.pop_back(); - }; - + void Pop() { variablesContainers.pop_back(); }; - /** Do not use - should be private but accessible to let Emscripten create a temporary. */ - VariablesContainersList(): firstLocalVariableContainerIndex(0) {}; - private: + /** Do not use - should be private but accessible to let Emscripten create a + * temporary. */ + VariablesContainersList() : firstLocalVariableContainerIndex(0){}; - std::vector variablesContainers; +private: + std::vector variablesContainers; std::size_t firstLocalVariableContainerIndex; static Variable badVariable; static VariablesContainer badVariablesContainer; }; -} // namespace gd \ No newline at end of file +} // namespace gd \ No newline at end of file diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp index 7f0fe0df5c09..66d7ee1b418e 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -131,10 +131,12 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode( bool compilationForRuntime) { gd::ObjectsContainer parameterObjectsAndGroups( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForFreeEventsFunction( project, eventsFunctionsExtension, eventsFunction, - parameterObjectsAndGroups); + parameterObjectsAndGroups, parameterVariablesContainer); EventsCodeGenerator codeGenerator(projectScopedContainers); codeGenerator.SetCodeNamespace(codeNamespace); @@ -185,10 +187,15 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode( bool compilationForRuntime) { gd::ObjectsContainer parameterObjectsContainers( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForBehaviorEventsFunction( project, eventsFunctionsExtension, eventsBasedBehavior, - eventsFunction, parameterObjectsContainers); + eventsFunction, parameterObjectsContainers, + parameterVariablesContainer, propertyVariablesContainer); EventsCodeGenerator codeGenerator(projectScopedContainers); codeGenerator.SetCodeNamespace(codeNamespace); @@ -265,10 +272,15 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode( bool compilationForRuntime) { gd::ObjectsContainer parameterObjectsContainers( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForObjectEventsFunction( project, eventsFunctionsExtension, eventsBasedObject, eventsFunction, - parameterObjectsContainers); + parameterObjectsContainers, parameterVariablesContainer, + propertyVariablesContainer); EventsCodeGenerator codeGenerator(projectScopedContainers); codeGenerator.SetCodeNamespace(codeNamespace); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 278adb2abeeb..67541c30ce8e 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -302,11 +302,12 @@ enum VariablesContainer_SourceType { "VariablesContainer::Object", "VariablesContainer::Local", "VariablesContainer::ExtensionGlobal", - "VariablesContainer::ExtensionScene" + "VariablesContainer::ExtensionScene", + "VariablesContainer::Parameters", + "VariablesContainer::Properties", }; interface VariablesContainer { - void VariablesContainer(); void VariablesContainer(VariablesContainer_SourceType sourceType); VariablesContainer_SourceType GetSourceType(); @@ -706,21 +707,26 @@ interface ProjectScopedContainers { [Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsFunction eventsFunction, - [Ref] ObjectsContainer parameterObjectsContainer); + [Ref] ObjectsContainer parameterObjectsContainer, + [Ref] VariablesContainer parameterVariablesContainer); [Value] ProjectScopedContainers STATIC_MakeNewProjectScopedContainersForBehaviorEventsFunction( [Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior, [Const, Ref] EventsFunction eventsFunction, - [Ref] ObjectsContainer parameterObjectsContainer); + [Ref] ObjectsContainer parameterObjectsContainer, + [Ref] VariablesContainer parameterVariablesContainer, + [Ref] VariablesContainer propertyVariablesContainer); [Value] ProjectScopedContainers STATIC_MakeNewProjectScopedContainersForObjectEventsFunction( [Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedObject eventsBasedObject, [Const, Ref] EventsFunction eventsFunction, - [Ref] ObjectsContainer parameterObjectsContainer); + [Ref] ObjectsContainer parameterObjectsContainer, + [Ref] VariablesContainer parameterVariablesContainer, + [Ref] VariablesContainer propertyVariablesContainer); [Value] ProjectScopedContainers STATIC_MakeNewProjectScopedContainersForEventsBasedObject( [Const, Ref] Project project, diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 44e20fbf3988..693d5c2b9204 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -125,7 +125,7 @@ type Variable_Type = 0 | 1 | 2 | 3 | 4 | 5 | 6` fs.writeFileSync( 'types/variablescontainer_sourcetype.js', `// Automatically generated by GDevelop.js/scripts/generate-types.js -type VariablesContainer_SourceType = 0 | 1 | 2 | 3 | 4 | 5 | 6` +type VariablesContainer_SourceType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8` ); shell.sed( '-i', @@ -139,6 +139,8 @@ type VariablesContainer_SourceType = 0 | 1 | 2 | 3 | 4 | 5 | 6` ' static Local: 4;', ' static ExtensionGlobal: 5;', ' static ExtensionScene: 6;', + ' static Parameters: 7;', + ' static Properties: 8;', ].join('\n'), 'types/gdvariablescontainer.js' ); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 2146cb0371bd..71d151565501 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -37,6 +37,8 @@ export enum VariablesContainer_SourceType { Local = 4, ExtensionGlobal = 5, ExtensionScene = 6, + Parameters = 7, + Properties = 8, } export enum ObjectsContainer_SourceType { @@ -329,7 +331,6 @@ export class Variable extends EmscriptenObject { } export class VariablesContainer extends EmscriptenObject { - constructor(); constructor(sourceType: VariablesContainer_SourceType); getSourceType(): VariablesContainer_SourceType; has(name: string): boolean; @@ -632,9 +633,9 @@ export class ProjectScopedContainers extends EmscriptenObject { static makeNewProjectScopedContainersForProjectAndLayout(project: Project, layout: Layout): ProjectScopedContainers; static makeNewProjectScopedContainersForProject(project: Project): ProjectScopedContainers; static makeNewProjectScopedContainersForEventsFunctionsExtension(project: Project, eventsFunctionsExtension: EventsFunctionsExtension): ProjectScopedContainers; - static makeNewProjectScopedContainersForFreeEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer): ProjectScopedContainers; - static makeNewProjectScopedContainersForBehaviorEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer): ProjectScopedContainers; - static makeNewProjectScopedContainersForObjectEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer): ProjectScopedContainers; + static makeNewProjectScopedContainersForFreeEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, parameterVariablesContainer: VariablesContainer): ProjectScopedContainers; + static makeNewProjectScopedContainersForBehaviorEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, parameterVariablesContainer: VariablesContainer, propertyVariablesContainer: VariablesContainer): ProjectScopedContainers; + static makeNewProjectScopedContainersForObjectEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, parameterVariablesContainer: VariablesContainer, propertyVariablesContainer: VariablesContainer): ProjectScopedContainers; static makeNewProjectScopedContainersForEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, outputObjectsContainer: ObjectsContainer): ProjectScopedContainers; static makeNewProjectScopedContainersWithLocalVariables(projectScopedContainers: ProjectScopedContainers, event: BaseEvent): ProjectScopedContainers; addPropertiesContainer(propertiesContainer: PropertiesContainer): ProjectScopedContainers; diff --git a/GDevelop.js/types/gdprojectscopedcontainers.js b/GDevelop.js/types/gdprojectscopedcontainers.js index 91ea7ddaf72b..6c4de2bc292a 100644 --- a/GDevelop.js/types/gdprojectscopedcontainers.js +++ b/GDevelop.js/types/gdprojectscopedcontainers.js @@ -3,9 +3,9 @@ declare class gdProjectScopedContainers { static makeNewProjectScopedContainersForProjectAndLayout(project: gdProject, layout: gdLayout): gdProjectScopedContainers; static makeNewProjectScopedContainersForProject(project: gdProject): gdProjectScopedContainers; static makeNewProjectScopedContainersForEventsFunctionsExtension(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension): gdProjectScopedContainers; - static makeNewProjectScopedContainersForFreeEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer): gdProjectScopedContainers; - static makeNewProjectScopedContainersForBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer): gdProjectScopedContainers; - static makeNewProjectScopedContainersForObjectEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer): gdProjectScopedContainers; + static makeNewProjectScopedContainersForFreeEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, parameterVariablesContainer: gdVariablesContainer): gdProjectScopedContainers; + static makeNewProjectScopedContainersForBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, parameterVariablesContainer: gdVariablesContainer, propertyVariablesContainer: gdVariablesContainer): gdProjectScopedContainers; + static makeNewProjectScopedContainersForObjectEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, parameterVariablesContainer: gdVariablesContainer, propertyVariablesContainer: gdVariablesContainer): gdProjectScopedContainers; static makeNewProjectScopedContainersForEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, outputObjectsContainer: gdObjectsContainer): gdProjectScopedContainers; static makeNewProjectScopedContainersWithLocalVariables(projectScopedContainers: gdProjectScopedContainers, event: gdBaseEvent): gdProjectScopedContainers; addPropertiesContainer(propertiesContainer: gdPropertiesContainer): gdProjectScopedContainers; diff --git a/GDevelop.js/types/gdvariablescontainer.js b/GDevelop.js/types/gdvariablescontainer.js index 1a185cde4c23..0d08c3f3dc49 100644 --- a/GDevelop.js/types/gdvariablescontainer.js +++ b/GDevelop.js/types/gdvariablescontainer.js @@ -7,7 +7,8 @@ declare class gdVariablesContainer { static Local: 4; static ExtensionGlobal: 5; static ExtensionScene: 6; - constructor(): void; + static Parameters: 7; + static Properties: 8; constructor(sourceType: VariablesContainer_SourceType): void; getSourceType(): VariablesContainer_SourceType; has(name: string): boolean; diff --git a/GDevelop.js/types/variablescontainer_sourcetype.js b/GDevelop.js/types/variablescontainer_sourcetype.js index e5b437640c60..43110c0e44ba 100644 --- a/GDevelop.js/types/variablescontainer_sourcetype.js +++ b/GDevelop.js/types/variablescontainer_sourcetype.js @@ -1,2 +1,2 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js -type VariablesContainer_SourceType = 0 | 1 | 2 | 3 | 4 | 5 | 6 \ No newline at end of file +type VariablesContainer_SourceType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 \ No newline at end of file diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js index 1c1effdaa48e..37cc0abba169 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js @@ -160,6 +160,12 @@ export default class EventsFunctionsExtensionEditor extends React.Component< _objectsContainer: gdObjectsContainer = new gd.ObjectsContainer( gd.ObjectsContainer.Function ); + _parameterVariablesContainer: gdVariablesContainer = new gd.VariablesContainer( + gd.VariablesContainer.Parameters + ); + _propertyVariablesContainer: gdVariablesContainer = new gd.VariablesContainer( + gd.VariablesContainer.Properties + ); _projectScopedContainersAccessor: ProjectScopedContainersAccessor | null = null; componentDidMount() { @@ -211,7 +217,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component< }; this._projectScopedContainersAccessor = new ProjectScopedContainersAccessor( scope, - this._objectsContainer + this._objectsContainer, + this._parameterVariablesContainer, + this._propertyVariablesContainer ); }; diff --git a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableField.js b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableField.js index 116f887bc64b..a4093362d300 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableField.js @@ -16,6 +16,8 @@ import { import { enumerateVariablesOfContainersList } from './EnumerateVariables'; import { mapFor } from '../../Utils/MapFor'; +const gd: libGDevelop = global.gd; + export default React.forwardRef( function AnyVariableField(props: ParameterFieldProps, ref) { const field = React.useRef(null); @@ -60,6 +62,12 @@ export default React.forwardRef( i => { return variablesContainersList.getVariablesContainer(i); } + ).filter( + variableContainer => + variableContainer.getSourceType() !== + gd.VariablesContainer.Parameters && + variableContainer.getSourceType() !== + gd.VariablesContainer.Properties ); }, [projectScopedContainersAccessor] diff --git a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js index 02821b318d9f..b40e60cec457 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js @@ -36,8 +36,9 @@ export type EnumeratedVariable = {| const variableSort = [variable => variable.name.toLowerCase()]; -export const enumerateVariablesOfContainersList = ( - variablesContainersList: ?gdVariablesContainersList +const enumerateVariablesOfContainersListExcludingSourceTypes = ( + variablesContainersList: ?gdVariablesContainersList, + excludedSourceType: Array ): Array => { if (!variablesContainersList) { return []; @@ -51,9 +52,15 @@ export const enumerateVariablesOfContainersList = ( 0, variablesContainersList.getVariablesContainersCount(), i => { - return enumerateVariables( - variablesContainersList.getVariablesContainer(i) + const variablesContainer = variablesContainersList.getVariablesContainer( + i ); + if ( + excludedSourceType.includes(variablesContainer.getSourceType()) + ) { + return []; + } + return enumerateVariables(variablesContainer); } ) ) @@ -64,6 +71,15 @@ export const enumerateVariablesOfContainersList = ( ); }; +export const enumerateVariablesOfContainersList = ( + variablesContainersList: ?gdVariablesContainersList +): Array => { + return enumerateVariablesOfContainersListExcludingSourceTypes( + variablesContainersList, + [gd.VariablesContainer.Parameters, gd.VariablesContainer.Properties] + ); +}; + export const enumerateVariables = ( variablesContainer: ?gdVariablesContainer ): Array => { diff --git a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.spec.js b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.spec.js index 1ce7db60eb54..9626b56fe9db 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.spec.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.spec.js @@ -4,7 +4,7 @@ const gd: libGDevelop = global.gd; describe('EnumerateVariables', () => { it('can enumerate variables, including children', () => { - const container = new gd.VariablesContainer(); + const container = new gd.VariablesContainer(gd.VariablesContainer.Unknown); container .insert('Variable1', new gd.Variable(), 0) .setString('A multiline\nstr value'); @@ -44,7 +44,7 @@ describe('EnumerateVariables', () => { expect(allNames).toContain('Variable4[2][2]'); }); it('can enumerate "invalid" variable names, including children', () => { - const container = new gd.VariablesContainer(); + const container = new gd.VariablesContainer(gd.VariablesContainer.Unknown); container.insert('ValidName', new gd.Variable(), 0); container.insert('Invalid!Name', new gd.Variable(), 1); const variable3 = new gd.Variable(); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js b/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js index 1280524984dc..412b02de7493 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/VariableField.js @@ -38,6 +38,8 @@ import GlobalVariableIcon from '../../UI/CustomSvgIcons/GlobalVariable'; import SceneVariableIcon from '../../UI/CustomSvgIcons/SceneVariable'; import ObjectVariableIcon from '../../UI/CustomSvgIcons/ObjectVariable'; import LocalVariableIcon from '../../UI/CustomSvgIcons/LocalVariable'; +import PropertyIcon from '../../UI/CustomSvgIcons/Settings'; +import ParameterIcon from '../../UI/CustomSvgIcons/Parameter'; import { ProjectScopedContainersAccessor } from '../../InstructionOrExpression/EventsScope'; import Link from '../../UI/Link'; import Add from '../../UI/CustomSvgIcons/Add'; @@ -159,6 +161,10 @@ export const getVariableSourceIcon = ( return ObjectVariableIcon; case gd.VariablesContainer.Local: return LocalVariableIcon; + case gd.VariablesContainer.Parameters: + return ParameterIcon; + case gd.VariablesContainer.Properties: + return PropertyIcon; default: return UnknownTypeIcon; } diff --git a/newIDE/app/src/InstructionOrExpression/EventsScope.js b/newIDE/app/src/InstructionOrExpression/EventsScope.js index 7684f334b5a7..d1b71f700fd8 100644 --- a/newIDE/app/src/InstructionOrExpression/EventsScope.js +++ b/newIDE/app/src/InstructionOrExpression/EventsScope.js @@ -17,15 +17,21 @@ export type EventsScope = {| export class ProjectScopedContainersAccessor { _scope: EventsScope; _parameterObjectsContainer: gdObjectsContainer | null; + _parameterVariablesContainer: gdVariablesContainer | null; + _propertyVariablesContainer: gdVariablesContainer | null; _eventPath: Array; constructor( scope: EventsScope, parameterObjectsContainer: gdObjectsContainer | null = null, + parameterVariablesContainer: gdVariablesContainer | null = null, + propertyVariablesContainer: gdVariablesContainer | null = null, eventPath: Array = [] ) { this._scope = scope; this._parameterObjectsContainer = parameterObjectsContainer; + this._parameterVariablesContainer = parameterVariablesContainer; + this._propertyVariablesContainer = propertyVariablesContainer; this._eventPath = eventPath; // Trigger parameterObjectsContainer update. this.get(); @@ -57,28 +63,48 @@ export class ProjectScopedContainersAccessor { if (!this._parameterObjectsContainer) { throw new Error('Extension scope used without any ObjectsContainer'); } + if (!this._parameterVariablesContainer) { + throw new Error( + 'Extension scope used without a VariablesContainer for parameters' + ); + } if (eventsBasedBehavior) { + if (!this._propertyVariablesContainer) { + throw new Error( + 'Extension scope used without a VariablesContainer for properties' + ); + } projectScopedContainers = gd.ProjectScopedContainers.makeNewProjectScopedContainersForBehaviorEventsFunction( project, eventsFunctionsExtension, eventsBasedBehavior, eventsFunction, - this._parameterObjectsContainer + this._parameterObjectsContainer, + this._parameterVariablesContainer, + this._propertyVariablesContainer ); } else if (eventsBasedObject) { + if (!this._propertyVariablesContainer) { + throw new Error( + 'Extension scope used without a VariablesContainer for properties' + ); + } projectScopedContainers = gd.ProjectScopedContainers.makeNewProjectScopedContainersForObjectEventsFunction( project, eventsFunctionsExtension, eventsBasedObject, eventsFunction, - this._parameterObjectsContainer + this._parameterObjectsContainer, + this._parameterVariablesContainer, + this._propertyVariablesContainer ); } else { projectScopedContainers = gd.ProjectScopedContainers.makeNewProjectScopedContainersForFreeEventsFunction( project, eventsFunctionsExtension, eventsFunction, - this._parameterObjectsContainer + this._parameterObjectsContainer, + this._parameterVariablesContainer ); } } else if (eventsBasedObject) { @@ -116,6 +142,8 @@ export class ProjectScopedContainersAccessor { return new ProjectScopedContainersAccessor( this._scope, this._parameterObjectsContainer, + this._parameterVariablesContainer, + this._propertyVariablesContainer, [...this._eventPath, event] ); } diff --git a/newIDE/app/src/VariablesList/VariableToTreeNodeHandling.spec.js b/newIDE/app/src/VariablesList/VariableToTreeNodeHandling.spec.js index c47751b99dae..269caa354160 100644 --- a/newIDE/app/src/VariablesList/VariableToTreeNodeHandling.spec.js +++ b/newIDE/app/src/VariablesList/VariableToTreeNodeHandling.spec.js @@ -73,7 +73,9 @@ describe('VariableToTreeNodeHandling', () => { parent2.insertChild('structureChild', structureChild); - variablesContainer = new gd.VariablesContainer(); + variablesContainer = new gd.VariablesContainer( + gd.VariablesContainer.Unknown + ); variablesContainer.insert('parent', parent, 0); variablesContainer.insert('parent2', parent2, 1); }); From d06de4f66f672533150ca0a18dab6eb5e0757f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 2 Nov 2024 21:17:47 +0100 Subject: [PATCH 02/26] Add new parameter type variableOrPropertyOrParameter and variableOrProperty. --- .../Extensions/Builtin/VariablesExtension.cpp | 12 +- .../Extensions/Metadata/ValueTypeMetadata.h | 11 +- .../AnyVariableOrPropertyField.js | 142 ++++++++++++++++++ .../AnyVariableOrPropertyOrParameterField.js | 138 +++++++++++++++++ .../ParameterFields/EnumerateVariables.js | 18 +++ .../EventsSheet/ParameterRenderingService.js | 10 ++ .../app/src/UI/Theme/Global/EventsSheet.css | 8 + 7 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyField.js create mode 100644 newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js diff --git a/Core/GDCore/Extensions/Builtin/VariablesExtension.cpp b/Core/GDCore/Extensions/Builtin/VariablesExtension.cpp index 7e26f4f3b7a4..228abbcd6d1c 100644 --- a/Core/GDCore/Extensions/Builtin/VariablesExtension.cpp +++ b/Core/GDCore/Extensions/Builtin/VariablesExtension.cpp @@ -33,7 +33,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/conditions/var24.png", "res/conditions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrPropertyOrParameter", _("Variable")) .UseStandardRelationalOperatorParameters( "number", ParameterOptions::MakeNewOptions()); @@ -45,7 +45,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/conditions/var24.png", "res/conditions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrPropertyOrParameter", _("Variable")) .UseStandardRelationalOperatorParameters( "string", ParameterOptions::MakeNewOptions()); @@ -58,7 +58,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/conditions/var24.png", "res/conditions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrPropertyOrParameter", _("Variable")) .AddParameter("trueorfalse", _("Check if the value is")) .SetDefaultValue("true") // This parameter allows to keep the operand expression @@ -73,7 +73,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/actions/var24.png", "res/actions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrProperty", _("Variable")) .UseStandardOperatorParameters("number", ParameterOptions::MakeNewOptions()); @@ -85,7 +85,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/actions/var24.png", "res/actions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrProperty", _("Variable")) .UseStandardOperatorParameters("string", ParameterOptions::MakeNewOptions()); @@ -98,7 +98,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension( "", "res/conditions/var24.png", "res/conditions/var.png") - .AddParameter("variable", _("Variable")) + .AddParameter("variableOrProperty", _("Variable")) .AddParameter("operator", _("Value"), "boolean") // This parameter allows to keep the operand expression // when the editor switch between variable instructions. diff --git a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h index ccc37cd693d1..6b311fae10d4 100644 --- a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h @@ -207,10 +207,13 @@ class GD_CORE_API ValueTypeMetadata { return parameterType == "yesorno" || parameterType == "trueorfalse"; } else if (type == "variable") { return - parameterType == "variable" || // Any variable. - // Old, "pre-scoped" variables: - parameterType == "objectvar" || parameterType == "globalvar" || - parameterType == "scenevar"; + // Any variable. + parameterType == "variable" || + parameterType == "variableOrProperty" || + parameterType == "variableOrPropertyOrParameter" || + // Old, "pre-scoped" variables: + parameterType == "objectvar" || parameterType == "globalvar" || + parameterType == "scenevar"; } else if (type == "resource") { return parameterType == "fontResource" || parameterType == "audioResource" || diff --git a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyField.js b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyField.js new file mode 100644 index 000000000000..a352d2a27a3a --- /dev/null +++ b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyField.js @@ -0,0 +1,142 @@ +// @flow +import * as React from 'react'; +import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow'; +import VariableField, { + getRootVariableName, + renderVariableWithIcon, + type VariableFieldInterface, + type VariableDialogOpeningProps, +} from './VariableField'; +import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog'; +import { + type ParameterFieldProps, + type ParameterFieldInterface, + type FieldFocusFunction, +} from './ParameterFieldCommons'; +import { enumerateVariablesOrPropertiesOfContainersList } from './EnumerateVariables'; +import { mapFor } from '../../Utils/MapFor'; + +const gd: libGDevelop = global.gd; + +export default React.forwardRef( + function AnyVariableField(props: ParameterFieldProps, ref) { + const field = React.useRef(null); + const [ + editorOpen, + setEditorOpen, + ] = React.useState(null); + const focus: FieldFocusFunction = options => { + if (field.current) field.current.focus(options); + }; + React.useImperativeHandle(ref, () => ({ + focus, + })); + + const { + project, + scope, + instruction, + onInstructionTypeChanged, + projectScopedContainersAccessor, + onChange, + value, + } = props; + const { layout } = scope; + + const enumerateGlobalAndSceneVariables = React.useCallback( + () => + enumerateVariablesOrPropertiesOfContainersList( + projectScopedContainersAccessor.get().getVariablesContainersList() + ), + [projectScopedContainersAccessor] + ); + + const variablesContainers = React.useMemo( + () => { + const variablesContainersList = projectScopedContainersAccessor + .get() + .getVariablesContainersList(); + return mapFor( + 0, + variablesContainersList.getVariablesContainersCount(), + i => { + return variablesContainersList.getVariablesContainer(i); + } + ).filter( + variableContainer => + variableContainer.getSourceType() !== + gd.VariablesContainer.Parameters + ); + }, + [projectScopedContainersAccessor] + ); + + const onVariableEditorApply = React.useCallback( + (selectedVariableName: string | null) => { + if (selectedVariableName && selectedVariableName.startsWith(value)) { + onChange(selectedVariableName); + } + setEditorOpen(null); + // The variable editor may have refactor the events for a variable type + // change which may have change the currently edited instruction type. + if (onInstructionTypeChanged) onInstructionTypeChanged(); + if (field.current) field.current.updateAutocompletions(); + }, + [onChange, onInstructionTypeChanged, value] + ); + + const isGlobal = !!( + layout && + project && + !layout.getVariables().has(getRootVariableName(props.value)) && + project.getVariables().has(getRootVariableName(props.value)) + ); + + return ( + + + {editorOpen && ( + setEditorOpen(null)} + onApply={onVariableEditorApply} + isGlobalTabInitiallyOpen={isGlobal} + initiallySelectedVariableName={editorOpen.variableName} + shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate} + hotReloadPreviewButtonProps={null} + /> + )} + + ); + } +); + +export const renderInlineAnyVariableOrProperty = ( + props: ParameterInlineRendererProps +) => renderVariableWithIcon(props, 'variable'); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js new file mode 100644 index 000000000000..363ff2e8cd57 --- /dev/null +++ b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js @@ -0,0 +1,138 @@ +// @flow +import * as React from 'react'; +import { type ParameterInlineRendererProps } from './ParameterInlineRenderer.flow'; +import VariableField, { + getRootVariableName, + renderVariableWithIcon, + type VariableFieldInterface, + type VariableDialogOpeningProps, +} from './VariableField'; +import GlobalAndSceneVariablesDialog from '../../VariablesList/GlobalAndSceneVariablesDialog'; +import { + type ParameterFieldProps, + type ParameterFieldInterface, + type FieldFocusFunction, +} from './ParameterFieldCommons'; +import { enumerateVariablesOrPropertiesOrParametersOfContainersList } from './EnumerateVariables'; +import { mapFor } from '../../Utils/MapFor'; + +const gd: libGDevelop = global.gd; + +export default React.forwardRef( + function AnyVariableField(props: ParameterFieldProps, ref) { + const field = React.useRef(null); + const [ + editorOpen, + setEditorOpen, + ] = React.useState(null); + const focus: FieldFocusFunction = options => { + if (field.current) field.current.focus(options); + }; + React.useImperativeHandle(ref, () => ({ + focus, + })); + + const { + project, + scope, + instruction, + onInstructionTypeChanged, + projectScopedContainersAccessor, + onChange, + value, + } = props; + const { layout } = scope; + + const enumerateGlobalAndSceneVariables = React.useCallback( + () => + enumerateVariablesOrPropertiesOrParametersOfContainersList( + projectScopedContainersAccessor.get().getVariablesContainersList() + ), + [projectScopedContainersAccessor] + ); + + const variablesContainers = React.useMemo( + () => { + const variablesContainersList = projectScopedContainersAccessor + .get() + .getVariablesContainersList(); + return mapFor( + 0, + variablesContainersList.getVariablesContainersCount(), + i => { + return variablesContainersList.getVariablesContainer(i); + } + ); + }, + [projectScopedContainersAccessor] + ); + + const onVariableEditorApply = React.useCallback( + (selectedVariableName: string | null) => { + if (selectedVariableName && selectedVariableName.startsWith(value)) { + onChange(selectedVariableName); + } + setEditorOpen(null); + // The variable editor may have refactor the events for a variable type + // change which may have change the currently edited instruction type. + if (onInstructionTypeChanged) onInstructionTypeChanged(); + if (field.current) field.current.updateAutocompletions(); + }, + [onChange, onInstructionTypeChanged, value] + ); + + const isGlobal = !!( + layout && + project && + !layout.getVariables().has(getRootVariableName(props.value)) && + project.getVariables().has(getRootVariableName(props.value)) + ); + + return ( + + + {editorOpen && ( + setEditorOpen(null)} + onApply={onVariableEditorApply} + isGlobalTabInitiallyOpen={isGlobal} + initiallySelectedVariableName={editorOpen.variableName} + shouldCreateInitiallySelectedVariable={editorOpen.shouldCreate} + hotReloadPreviewButtonProps={null} + /> + )} + + ); + } +); + +export const renderInlineAnyVariableOrPropertyOrParameter = ( + props: ParameterInlineRendererProps +) => renderVariableWithIcon(props, 'variable'); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js index b40e60cec457..27a9d7febd0f 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/EnumerateVariables.js @@ -80,6 +80,24 @@ export const enumerateVariablesOfContainersList = ( ); }; +export const enumerateVariablesOrPropertiesOfContainersList = ( + variablesContainersList: ?gdVariablesContainersList +): Array => { + return enumerateVariablesOfContainersListExcludingSourceTypes( + variablesContainersList, + [gd.VariablesContainer.Parameters] + ); +}; + +export const enumerateVariablesOrPropertiesOrParametersOfContainersList = ( + variablesContainersList: ?gdVariablesContainersList +): Array => { + return enumerateVariablesOfContainersListExcludingSourceTypes( + variablesContainersList, + [] + ); +}; + export const enumerateVariables = ( variablesContainer: ?gdVariablesContainer ): Array => { diff --git a/newIDE/app/src/EventsSheet/ParameterRenderingService.js b/newIDE/app/src/EventsSheet/ParameterRenderingService.js index ed6a6588b8f8..d56b117b6237 100644 --- a/newIDE/app/src/EventsSheet/ParameterRenderingService.js +++ b/newIDE/app/src/EventsSheet/ParameterRenderingService.js @@ -30,6 +30,12 @@ import BehaviorField from './ParameterFields/BehaviorField'; import AnyVariableField, { renderInlineAnyVariable, } from './ParameterFields/AnyVariableField'; +import AnyVariableOrPropertyField, { + renderInlineAnyVariableOrProperty, +} from './ParameterFields/AnyVariableOrPropertyField'; +import AnyVariableOrPropertyOrParameterField, { + renderInlineAnyVariableOrPropertyOrParameter, +} from './ParameterFields/AnyVariableOrPropertyOrParameterField'; import SceneVariableField, { renderInlineSceneVariable, } from './ParameterFields/SceneVariableField'; @@ -86,6 +92,8 @@ const components = { stringWithSelector: StringWithSelectorField, behavior: BehaviorField, variable: AnyVariableField, + variableOrProperty: AnyVariableOrPropertyField, + variableOrPropertyOrParameter: AnyVariableOrPropertyOrParameterField, scenevar: SceneVariableField, globalvar: GlobalVariableField, objectvar: ObjectVariableField, @@ -123,6 +131,8 @@ const inlineRenderers: { [string]: ParameterInlineRenderer } = { default: renderInlineDefaultField, forceMultiplier: renderInlineForceMultiplier, variable: renderInlineAnyVariable, + variableOrProperty: renderInlineAnyVariableOrProperty, + variableOrPropertyOrParameter: renderInlineAnyVariableOrPropertyOrParameter, globalvar: renderInlineGlobalVariable, scenevar: renderInlineSceneVariable, objectvar: renderInlineObjectVariable, diff --git a/newIDE/app/src/UI/Theme/Global/EventsSheet.css b/newIDE/app/src/UI/Theme/Global/EventsSheet.css index a1ee04b37a76..ec3063e6887d 100644 --- a/newIDE/app/src/UI/Theme/Global/EventsSheet.css +++ b/newIDE/app/src/UI/Theme/Global/EventsSheet.css @@ -183,6 +183,14 @@ color: var(--event-sheet-instruction-parameter-var-color); } +.gd-events-sheet .instruction-parameter.variableOrProperty { + color: var(--event-sheet-instruction-parameter-var-color); +} + +.gd-events-sheet .instruction-parameter.variableOrPropertyOrParameter { + color: var(--event-sheet-instruction-parameter-var-color); +} + .gd-events-sheet .instruction-parameter .instruction-invalid-parameter { color: var(--event-sheet-instruction-parameter-error-color); text-decoration: var(--event-sheet-instruction-parameter-error-color) underline wavy; From 9278d9c5145b7c0cdeff16c8a948ee5adf464d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 3 Nov 2024 17:59:34 +0100 Subject: [PATCH 03/26] Add code generation. --- .../CodeGeneration/EventsCodeGenerator.cpp | 18 ++ .../CodeGeneration/EventsCodeGenerator.h | 12 + .../ExpressionCodeGenerator.cpp | 42 ++-- Core/GDCore/Project/ProjectScopedContainers.h | 12 +- .../CodeGeneration/EventsCodeGenerator.cpp | 72 +++++- .../CodeGeneration/EventsCodeGenerator.h | 12 + .../Extensions/Builtin/VariablesExtension.cpp | 216 +++++++++++++++--- .../AnyVariableOrPropertyOrParameterField.js | 2 - 8 files changed, 320 insertions(+), 66 deletions(-) diff --git a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.cpp b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.cpp index 662d3f2a1bf5..107fb90eefb3 100644 --- a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -1358,12 +1358,30 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesConta return "getProperty" + property.GetName() + "As" + type + "()"; } +gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property) { + return "getProperty" + property.GetName() + "()"; +} + +gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property, + const gd::String &operandCode) { + return "setProperty" + property.GetName() + "(" + operandCode + ")"; + } + gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter, const gd::String& type, gd::EventsCodeGenerationContext& context) { return "getParameter" + parameter.GetName() + "As" + type + "()"; } +gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting( + const gd::ParameterMetadata ¶meter) { + return "getParameter" + parameter.GetName() + "()"; + } + EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_, const gd::Layout& layout, const gd::Platform& platform_) diff --git a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h index bc942c59a2ea..2e4f5e0b38f5 100644 --- a/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h +++ b/Core/GDCore/Events/CodeGeneration/EventsCodeGenerator.h @@ -510,6 +510,11 @@ class GD_CORE_API EventsCodeGenerator { GenerateAnyOrSceneVariableGetter(const gd::Expression &variableExpression, EventsCodeGenerationContext &context); + virtual gd::String GeneratePropertySetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property, + const gd::String &operandCode); + protected: virtual const gd::String GenerateRelationalOperatorCodes( const gd::String& operatorString); @@ -627,11 +632,18 @@ class GD_CORE_API EventsCodeGenerator { const gd::String& type, gd::EventsCodeGenerationContext& context); + virtual gd::String GeneratePropertyGetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property); + virtual gd::String GenerateParameterGetter( const gd::ParameterMetadata& parameter, const gd::String& type, gd::EventsCodeGenerationContext& context); + virtual gd::String + GenerateParameterGetterWithoutCasting(const gd::ParameterMetadata ¶meter); + /** * \brief Generate the code to reference an object which is * in an empty/null state. diff --git a/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp index a934fed8855b..2ac07c8bb9e3 100644 --- a/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp +++ b/Core/GDCore/Events/CodeGeneration/ExpressionCodeGenerator.cpp @@ -133,13 +133,12 @@ void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) { if (gd::ParameterMetadata::IsExpression("variable", type)) { // The node is a variable inside an expression waiting for a *variable* to be returned, not its value. EventsCodeGenerator::VariableScope scope = - type == "variable" + type == "variable" || type == "variableOrProperty" || + type == "variableOrPropertyOrParameter" ? gd::EventsCodeGenerator::ANY_VARIABLE - : type == "globalvar" - ? gd::EventsCodeGenerator::PROJECT_VARIABLE - : type == "scenevar" - ? gd::EventsCodeGenerator::LAYOUT_VARIABLE - : gd::EventsCodeGenerator::OBJECT_VARIABLE; + : type == "globalvar" ? gd::EventsCodeGenerator::PROJECT_VARIABLE + : type == "scenevar" ? gd::EventsCodeGenerator::LAYOUT_VARIABLE + : gd::EventsCodeGenerator::OBJECT_VARIABLE; auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(), codeGenerator.GetObjectsContainersList(), @@ -222,23 +221,22 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) { output += codeGenerator.GenerateObject(node.identifierName, type, context); } else if (gd::ParameterMetadata::IsExpression("variable", type)) { - EventsCodeGenerator::VariableScope scope = - type == "variable" + EventsCodeGenerator::VariableScope scope = + type == "variable" || type == "variableOrProperty" || + type == "variableOrPropertyOrParameter" ? gd::EventsCodeGenerator::ANY_VARIABLE - : type == "globalvar" - ? gd::EventsCodeGenerator::PROJECT_VARIABLE - : type == "scenevar" - ? gd::EventsCodeGenerator::LAYOUT_VARIABLE - : gd::EventsCodeGenerator::OBJECT_VARIABLE; - - auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(), - codeGenerator.GetObjectsContainersList(), - rootObjectName, - node); - output += codeGenerator.GenerateGetVariable( - node.identifierName, scope, context, objectName); - if (!node.childIdentifierName.empty()) { - output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName); + : type == "globalvar" ? gd::EventsCodeGenerator::PROJECT_VARIABLE + : type == "scenevar" ? gd::EventsCodeGenerator::LAYOUT_VARIABLE + : gd::EventsCodeGenerator::OBJECT_VARIABLE; + + auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName( + codeGenerator.GetPlatform(), codeGenerator.GetObjectsContainersList(), + rootObjectName, node); + output += codeGenerator.GenerateGetVariable(node.identifierName, scope, + context, objectName); + if (!node.childIdentifierName.empty()) { + output += + codeGenerator.GenerateVariableAccessor(node.childIdentifierName); } } else { const auto& variablesContainersList = codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); diff --git a/Core/GDCore/Project/ProjectScopedContainers.h b/Core/GDCore/Project/ProjectScopedContainers.h index 85e819812490..3cb77988e02b 100644 --- a/Core/GDCore/Project/ProjectScopedContainers.h +++ b/Core/GDCore/Project/ProjectScopedContainers.h @@ -129,9 +129,17 @@ class ProjectScopedContainers { std::function notFoundCallback) const { if (objectsContainersList.HasObjectOrGroupNamed(name)) return objectCallback(); - else if (variablesContainersList.Has(name)) + else if (variablesContainersList.Has(name)) { + const auto &variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName(name); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType == gd::VariablesContainer::SourceType::Properties) { + return propertyCallback(); + } else if (sourceType == gd::VariablesContainer::SourceType::Parameters) { + return parameterCallback(); + } return variableCallback(); - else if (ParameterMetadataTools::Has(parametersVectorsList, name)) + } else if (ParameterMetadataTools::Has(parametersVectorsList, name)) return parameterCallback(); else if (propertiesContainersList.Has(name)) return propertyCallback(); diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp index 66d7ee1b418e..df849abf448b 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -1393,6 +1393,22 @@ gd::String EventsCodeGenerator::GenerateGetVariable( gd::VariablesContainer::SourceType::ExtensionScene) { variables = &variablesContainer; output = "eventsFunctionContext.sceneVariablesForExtension"; + } else if (sourceType == + gd::VariablesContainer::SourceType::Properties) { + const auto &propertiesContainersList = + GetProjectScopedContainers().GetPropertiesContainersList(); + const auto &propertiesContainerAndProperty = + propertiesContainersList.Get(variableName); + return GeneratePropertyGetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second); + } else if (sourceType == + gd::VariablesContainer::SourceType::Parameters) { + const auto ¶metersVectorsList = + GetProjectScopedContainers().GetParametersVectorsList(); + const auto ¶meter = + gd::ParameterMetadataTools::Get(parametersVectorsList, variableName); + return GenerateParameterGetterWithoutCasting(parameter); } } else if (scope == LAYOUT_VARIABLE) { output = "runtimeScene.getScene().getVariables()"; @@ -1501,11 +1517,10 @@ gd::String EventsCodeGenerator::GenerateProfilerSectionEnd( ConvertToStringExplicit(section) + "); }"; } -gd::String EventsCodeGenerator::GeneratePropertyGetter( - const gd::PropertiesContainer& propertiesContainer, - const gd::NamedPropertyDescriptor& property, - const gd::String& type, - gd::EventsCodeGenerationContext& context) { +gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property, + const gd::String &operandCode) { bool isLocalProperty = projectScopedContainers.GetPropertiesContainersList() .GetBottomMostPropertiesContainer() == &propertiesContainer; @@ -1518,6 +1533,34 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter( gd::EventsFunctionsContainer::Object ? "eventsFunctionContext.getObjects(\"Object\")[0]" : "eventsFunctionContext.getProperties()"); + + gd::String propertySetterCode = + propertyHolderCode + "." + + (isLocalProperty + ? BehaviorCodeGenerator::GetBehaviorPropertySetterName( + property.GetName()) + : BehaviorCodeGenerator::GetBehaviorSharedPropertySetterName( + property.GetName())) + + "(" + operandCode + ")"; + return propertySetterCode; +} + +gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property) { + bool isLocalProperty = + projectScopedContainers.GetPropertiesContainersList() + .GetBottomMostPropertiesContainer() == &propertiesContainer; + + gd::String propertyHolderCode = + propertiesContainer.GetOwner() == gd::EventsFunctionsContainer::Behavior + ? "eventsFunctionContext.getObjects(\"Object\")[0].getBehavior(" + + GenerateGetBehaviorNameCode("Behavior") + ")" + : (propertiesContainer.GetOwner() == + gd::EventsFunctionsContainer::Object + ? "eventsFunctionContext.getObjects(\"Object\")[0]" + : "eventsFunctionContext.getProperties()"); + gd::String propertyGetterCode = propertyHolderCode + "." + (isLocalProperty @@ -1526,6 +1569,16 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter( : BehaviorCodeGenerator::GetBehaviorSharedPropertyGetterName( property.GetName())) + "()"; + return propertyGetterCode; +} + +gd::String EventsCodeGenerator::GeneratePropertyGetter( + const gd::PropertiesContainer& propertiesContainer, + const gd::NamedPropertyDescriptor& property, + const gd::String& type, + gd::EventsCodeGenerationContext& context) { + gd::String propertyGetterCode = + GeneratePropertyGetterWithoutCasting(propertiesContainer, property); if (type == "number|string") { if (property.GetType() == "Number") { @@ -1560,13 +1613,18 @@ gd::String EventsCodeGenerator::GeneratePropertyGetter( } } +gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting( + const gd::ParameterMetadata ¶meter) { + return "eventsFunctionContext.getArgument(" + + ConvertToStringExplicit(parameter.GetName()) + ")"; +} + gd::String EventsCodeGenerator::GenerateParameterGetter( const gd::ParameterMetadata& parameter, const gd::String& type, gd::EventsCodeGenerationContext& context) { gd::String parameterGetterCode = - "eventsFunctionContext.getArgument(" + - ConvertToStringExplicit(parameter.GetName()) + ")"; + GenerateParameterGetterWithoutCasting(parameter); if (type == "number|string") { if (parameter.GetValueTypeMetadata().IsNumber()) { diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h index 35caa16a4ebf..9518f7f25c90 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.h @@ -225,6 +225,11 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { codeNamespace = codeNamespace_; }; + virtual gd::String GeneratePropertySetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property, + const gd::String &operandCode) override; + protected: virtual gd::String GenerateParameterCodes( const gd::Expression& parameter, @@ -329,10 +334,17 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator { const gd::String& type, gd::EventsCodeGenerationContext& context) override; + virtual gd::String GeneratePropertyGetterWithoutCasting( + const gd::PropertiesContainer &propertiesContainer, + const gd::NamedPropertyDescriptor &property) override; + virtual gd::String GenerateParameterGetter(const gd::ParameterMetadata& parameter, const gd::String& type, gd::EventsCodeGenerationContext& context) override; + virtual gd::String GenerateParameterGetterWithoutCasting( + const gd::ParameterMetadata ¶meter) override; + virtual gd::String GenerateBadObject() override { return "null"; } virtual gd::String GenerateObject(const gd::String& objectName, diff --git a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp index 67c478dfc213..793b19de3ed5 100644 --- a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp +++ b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp @@ -23,12 +23,71 @@ namespace gdjs { VariablesExtension::VariablesExtension() { gd::BuiltinExtensionsImplementer::ImplementsVariablesExtension(*this); - GetAllConditions()["NumberVariable"].SetFunctionName( - "gdjs.evtTools.variable.getVariableNumber"); - GetAllConditions()["StringVariable"].SetFunctionName( - "gdjs.evtTools.variable.getVariableString"); - GetAllConditions()["BooleanVariable"].SetFunctionName( - "gdjs.evtTools.variable.getVariableBoolean"); + GetAllConditions()["NumberVariable"].SetCustomCodeGenerator( + [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, + gd::EventsCodeGenerationContext &context) { + gd::String getterCode = + gd::ExpressionCodeGenerator::GenerateExpressionCode( + codeGenerator, context, "variable", + instruction.GetParameters()[0].GetPlainString()); + gd::String op = instruction.GetParameters()[1].GetPlainString(); + gd::String expressionCode = + gd::ExpressionCodeGenerator::GenerateExpressionCode( + codeGenerator, context, "number", + instruction.GetParameters()[2].GetPlainString()); + + gd::String resultingBoolean = + codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", + context); + + return resultingBoolean + " = " + + gd::String(instruction.IsInverted() ? "!" : "") + "(" + + codeGenerator.GenerateRelationalOperation(op, getterCode, + expressionCode) + + ");\n"; + }); + GetAllConditions()["StringVariable"].SetCustomCodeGenerator( + [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, + gd::EventsCodeGenerationContext &context) { + gd::String getterCode = + gd::ExpressionCodeGenerator::GenerateExpressionCode( + codeGenerator, context, "variable", + instruction.GetParameters()[0].GetPlainString()); + gd::String op = instruction.GetParameters()[1].GetPlainString(); + gd::String expressionCode = + gd::ExpressionCodeGenerator::GenerateExpressionCode( + codeGenerator, context, "string", + instruction.GetParameters()[2].GetPlainString()); + + gd::String resultingBoolean = + codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", + context); + + return resultingBoolean + " = " + + gd::String(instruction.IsInverted() ? "!" : "") + "(" + + codeGenerator.GenerateRelationalOperation(op, getterCode, + expressionCode) + + ");\n"; + }); + GetAllConditions()["BooleanVariable"].SetCustomCodeGenerator( + [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, + gd::EventsCodeGenerationContext &context) { + gd::String getterCode = + gd::ExpressionCodeGenerator::GenerateExpressionCode( + codeGenerator, context, "variable", + instruction.GetParameters()[0].GetPlainString()); + bool isOperandTrue = + instruction.GetParameters()[1].GetPlainString() == "True"; + + gd::String resultingBoolean = + codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", + context); + + return resultingBoolean + " = " + + gd::String(instruction.IsInverted() == isOperandTrue ? "!" + : "") + + getterCode + ";\n"; + }); GetAllStrExpressions()["VariableFirstString"].SetFunctionName( "gdjs.evtTools.variable.getFirstVariableString"); @@ -73,20 +132,55 @@ VariablesExtension::VariablesExtension() { [](gd::Instruction& instruction, gd::EventsCodeGenerator& codeGenerator, gd::EventsCodeGenerationContext& context) { - gd::String varGetter = + + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); + gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, "variable", - instruction.GetParameters()[0].GetPlainString()); - + variableName); gd::String op = instruction.GetParameters()[1].GetPlainString(); + + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType == gd::VariablesContainer::SourceType::Properties) { + const auto &propertiesContainersList = + codeGenerator.GetProjectScopedContainers().GetPropertiesContainersList(); + const auto &propertiesContainerAndProperty = + propertiesContainersList.Get(variableName); + + if (op == "True") { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + "true"); + } + else if (op == "False") { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + "false"); + } + else if (op == "Toggle") { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + "!" + getterCode); + } + return gd::String(""); + } + if (op == "True") - return varGetter + ".setBoolean(true);\n"; + return getterCode + ".setBoolean(true);\n"; else if (op == "False") - return varGetter + ".setBoolean(false);\n"; + return getterCode + ".setBoolean(false);\n"; else if (op == "Toggle") - return "gdjs.evtTools.variable.toggleVariableBoolean(" + varGetter + ");\n"; + return "gdjs.evtTools.variable.toggleVariableBoolean(" + getterCode + ");\n"; return gd::String(""); }); @@ -95,30 +189,58 @@ VariablesExtension::VariablesExtension() { [](gd::Instruction& instruction, gd::EventsCodeGenerator& codeGenerator, gd::EventsCodeGenerationContext& context) { - gd::String expressionCode = + + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); + gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, - "number", - instruction.GetParameters()[2].GetPlainString()); - gd::String varGetter = + "variable", + variableName); + gd::String op = instruction.GetParameters()[1].GetPlainString(); + gd::String expressionCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, - "variable", - instruction.GetParameters()[0].GetPlainString()); + "number", + instruction.GetParameters()[2].GetPlainString()); + + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType == gd::VariablesContainer::SourceType::Properties) { + const auto &propertiesContainersList = + codeGenerator.GetProjectScopedContainers().GetPropertiesContainersList(); + const auto &propertiesContainerAndProperty = + propertiesContainersList.Get(variableName); + + if (op == "=") { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + expressionCode); + } + else { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + getterCode + op + expressionCode); + } + } - gd::String op = instruction.GetParameters()[1].GetPlainString(); if (op == "=") - return varGetter + ".setNumber(" + expressionCode + ");\n"; + return getterCode + ".setNumber(" + expressionCode + ");\n"; else if (op == "+") - return varGetter + ".add(" + expressionCode + ");\n"; + return getterCode + ".add(" + expressionCode + ");\n"; else if (op == "-") - return varGetter + ".sub(" + expressionCode + ");\n"; + return getterCode + ".sub(" + expressionCode + ");\n"; else if (op == "*") - return varGetter + ".mul(" + expressionCode + ");\n"; + return getterCode + ".mul(" + expressionCode + ");\n"; else if (op == "/") - return varGetter + ".div(" + expressionCode + ");\n"; + return getterCode + ".div(" + expressionCode + ");\n"; return gd::String(""); }); @@ -127,24 +249,52 @@ VariablesExtension::VariablesExtension() { [](gd::Instruction& instruction, gd::EventsCodeGenerator& codeGenerator, gd::EventsCodeGenerationContext& context) { - gd::String expressionCode = + + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); + gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, - "string", - instruction.GetParameters()[2].GetPlainString()); - gd::String varGetter = + "variable", + variableName); + gd::String op = instruction.GetParameters()[1].GetPlainString(); + gd::String expressionCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, - "variable", - instruction.GetParameters()[0].GetPlainString()); + "string", + instruction.GetParameters()[2].GetPlainString()); + + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType == gd::VariablesContainer::SourceType::Properties) { + const auto &propertiesContainersList = + codeGenerator.GetProjectScopedContainers().GetPropertiesContainersList(); + const auto &propertiesContainerAndProperty = + propertiesContainersList.Get(variableName); + + if (op == "=") { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + expressionCode); + } + else { + return codeGenerator.GeneratePropertySetterWithoutCasting( + propertiesContainerAndProperty.first, + propertiesContainerAndProperty.second, + getterCode + op + expressionCode); + } + } - gd::String op = instruction.GetParameters()[1].GetPlainString(); if (op == "=") - return varGetter + ".setString(" + expressionCode + ");\n"; + return getterCode + ".setString(" + expressionCode + ");\n"; else if (op == "+") - return varGetter + ".concatenateString(" + expressionCode + ");\n"; + return getterCode + ".concatenateString(" + expressionCode + ");\n"; return gd::String(""); }); diff --git a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js index 363ff2e8cd57..2eae9458a193 100644 --- a/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js +++ b/newIDE/app/src/EventsSheet/ParameterFields/AnyVariableOrPropertyOrParameterField.js @@ -16,8 +16,6 @@ import { import { enumerateVariablesOrPropertiesOrParametersOfContainersList } from './EnumerateVariables'; import { mapFor } from '../../Utils/MapFor'; -const gd: libGDevelop = global.gd; - export default React.forwardRef( function AnyVariableField(props: ParameterFieldProps, ref) { const field = React.useRef(null); From a18caf0096936d5a8146158dce1ff7f90615053a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 3 Nov 2024 19:05:06 +0100 Subject: [PATCH 04/26] Fix expression validator. --- .../GDCore/IDE/Events/ExpressionValidator.cpp | 9 +++- Core/GDCore/IDE/Events/ExpressionValidator.h | 54 ++++++++++++------- .../src/EventsSheet/EventsTree/Instruction.js | 8 +++ 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/Core/GDCore/IDE/Events/ExpressionValidator.cpp b/Core/GDCore/IDE/Events/ExpressionValidator.cpp index dcb9e82c013b..3c2a1cb0e38d 100644 --- a/Core/GDCore/IDE/Events/ExpressionValidator.cpp +++ b/Core/GDCore/IDE/Events/ExpressionValidator.cpp @@ -460,6 +460,8 @@ const gd::String& ExpressionValidator::TypeToString(Type type) { case Type::NumberOrString: return numberOrStringTypeString; case Type::Variable: + case Type::VariableOrProperty: + case Type::VariableOrPropertyOrParameter: return variableTypeString; case Type::LegacyVariable: // This function is only used to display error. @@ -493,8 +495,11 @@ ExpressionValidator::Type ExpressionValidator::StringToType( ExpressionValidator::variableTypeString, type)) { if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) { return Type::LegacyVariable; - } - else { + } else if (type == "variableOrProperty") { + return Type::VariableOrProperty; + } else if (type == "variableOrPropertyOrParameter") { + return Type::VariableOrPropertyOrParameter; + } else { return Type::Variable; } } diff --git a/Core/GDCore/IDE/Events/ExpressionValidator.h b/Core/GDCore/IDE/Events/ExpressionValidator.h index f0348d687ee5..720ca41a95f3 100644 --- a/Core/GDCore/IDE/Events/ExpressionValidator.h +++ b/Core/GDCore/IDE/Events/ExpressionValidator.h @@ -203,7 +203,9 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { void OnVisitVariableNode(VariableNode& node) override { ReportAnyError(node); - if (parentType == Type::Variable) { + if (parentType == Type::Variable || + parentType == Type::VariableOrProperty || + parentType == Type::VariableOrPropertyOrParameter) { childType = parentType; CheckVariableExistence(node.location, node.name); @@ -216,7 +218,8 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { if (node.child) { node.child->Visit(*this); } - } else if (parentType == Type::String || parentType == Type::Number || parentType == Type::NumberOrString) { + } else if (parentType == Type::String || parentType == Type::Number || + parentType == Type::NumberOrString) { // The node represents a variable or an object variable in an expression waiting for its *value* to be returned. childType = parentType; @@ -336,11 +339,12 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { _("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."), node.location); } - } - else if (parentType == Type::Variable) { + } else if (parentType == Type::Variable || + parentType == Type::VariableOrProperty || + parentType == Type::VariableOrPropertyOrParameter) { CheckVariableExistence(node.location, node.identifierName); - } - else if (parentType != Type::Object && parentType != Type::LegacyVariable) { + } else if (parentType != Type::Object && + parentType != Type::LegacyVariable) { // It can't happen. RaiseTypeError( _("You've entered a name, but this type was expected:") + " " + TypeToString(parentType), @@ -376,8 +380,19 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { childType = Type::Empty; } - private: - enum Type {Unknown = 0, Number, String, NumberOrString, Variable, LegacyVariable, Object, Empty}; +private: + enum Type { + Unknown = 0, + Number, + String, + NumberOrString, + Variable, + LegacyVariable, + Object, + Empty, + VariableOrProperty, + VariableOrPropertyOrParameter + }; Type ValidateFunction(const gd::FunctionCallNode& function); bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier); bool ValidateObjectVariableOrVariableOrProperty( @@ -402,19 +417,22 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker { }, [&]() { // This is a property. - // This error won't happen unless the priority is changed. - RaiseVariableNameCollisionError( - _("This variable has the same name as a property. Consider " - "renaming one or the other."), - location, name); + if (parentType != Type::VariableOrProperty && + parentType != Type::VariableOrPropertyOrParameter) { + RaiseVariableNameCollisionError( + _("This variable has the same name as a property. Consider " + "renaming one or the other."), + location, name); + } }, [&]() { // This is a parameter. - // This error won't happen unless the priority is changed. - RaiseVariableNameCollisionError( - _("This variable has the same name as a parameter. Consider " - "renaming one or the other."), - location, name); + if (parentType != Type::VariableOrPropertyOrParameter) { + RaiseVariableNameCollisionError( + _("This variable has the same name as a parameter. Consider " + "renaming one or the other."), + location, name); + } }, [&]() { // This is something else. diff --git a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js index 4c2d14f26b5f..8c7d6a48f2ff 100644 --- a/newIDE/app/src/EventsSheet/EventsTree/Instruction.js +++ b/newIDE/app/src/EventsSheet/EventsTree/Instruction.js @@ -307,6 +307,14 @@ const Instruction = (props: Props) => { expressionNode.visit(expressionValidator); expressionIsValid = expressionValidator.getAllErrors().size() === 0; + if (!expressionIsValid) + console.log( + 'expressionIsValid: ' + + expressionValidator + .getAllErrors() + .at(0) + .getMessage() + ); expressionValidator.delete(); // New object variable instructions require the variable to be From ed4b7994d1066f3c99b8cf6e1881e0b53cd92661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 3 Nov 2024 19:09:11 +0100 Subject: [PATCH 05/26] Fix Core tests. --- Core/tests/WholeProjectRefactorer.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 3811b32c269b..603a7c6e4f81 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -1563,10 +1563,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // Create the objects container for the events function gd::ObjectsContainer parametersObjectsContainer( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForFreeEventsFunction( project, eventsExtension, eventsFunction, - parametersObjectsContainer); + parametersObjectsContainer, parameterVariablesContainer); // Trigger the refactoring after the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( @@ -1592,10 +1594,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // Create the objects container for the events function gd::ObjectsContainer parametersObjectsContainer( gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForFreeEventsFunction( project, eventsExtension, eventsFunction, - parametersObjectsContainer); + parametersObjectsContainer, parameterVariablesContainer); // Simulate a variable in ObjectWithMyBehavior, even if this is not // supported by the editor. From 59d9bab548245381cde4a03546d5bc7c431fe75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 3 Nov 2024 19:35:08 +0100 Subject: [PATCH 06/26] Fix GDevelop.js tests. --- .../Extensions/Builtin/VariablesExtension.cpp | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp index 793b19de3ed5..95d7839395e5 100644 --- a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp +++ b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp @@ -26,10 +26,11 @@ VariablesExtension::VariablesExtension() { GetAllConditions()["NumberVariable"].SetCustomCodeGenerator( [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, gd::EventsCodeGenerationContext &context) { + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, "variable", - instruction.GetParameters()[0].GetPlainString()); + variableName); gd::String op = instruction.GetParameters()[1].GetPlainString(); gd::String expressionCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( @@ -40,6 +41,17 @@ VariablesExtension::VariablesExtension() { codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", context); + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType != gd::VariablesContainer::SourceType::Properties && + sourceType != gd::VariablesContainer::SourceType::Parameters) { + getterCode += ".getAsNumber()"; + } + return resultingBoolean + " = " + gd::String(instruction.IsInverted() ? "!" : "") + "(" + codeGenerator.GenerateRelationalOperation(op, getterCode, @@ -49,6 +61,7 @@ VariablesExtension::VariablesExtension() { GetAllConditions()["StringVariable"].SetCustomCodeGenerator( [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, gd::EventsCodeGenerationContext &context) { + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, "variable", @@ -59,6 +72,17 @@ VariablesExtension::VariablesExtension() { codeGenerator, context, "string", instruction.GetParameters()[2].GetPlainString()); + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType != gd::VariablesContainer::SourceType::Properties && + sourceType != gd::VariablesContainer::SourceType::Parameters) { + getterCode += ".getAsString()"; + } + gd::String resultingBoolean = codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", context); @@ -72,12 +96,24 @@ VariablesExtension::VariablesExtension() { GetAllConditions()["BooleanVariable"].SetCustomCodeGenerator( [](gd::Instruction &instruction, gd::EventsCodeGenerator &codeGenerator, gd::EventsCodeGenerationContext &context) { + const auto &variableName = instruction.GetParameters()[0].GetPlainString(); gd::String getterCode = gd::ExpressionCodeGenerator::GenerateExpressionCode( codeGenerator, context, "variable", instruction.GetParameters()[0].GetPlainString()); bool isOperandTrue = - instruction.GetParameters()[1].GetPlainString() == "True"; + instruction.GetParameters()[1].GetPlainString() != "False"; + + const auto variablesContainersList = + codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); + const auto& variablesContainer = + variablesContainersList.GetVariablesContainerFromVariableName( + variableName); + const auto sourceType = variablesContainer.GetSourceType(); + if (sourceType != gd::VariablesContainer::SourceType::Properties && + sourceType != gd::VariablesContainer::SourceType::Parameters) { + getterCode += ".getAsBoolean()"; + } gd::String resultingBoolean = codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", From db2b19018e893d0f43691b33cee619cc92c4e82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 16 Nov 2024 22:05:41 +0100 Subject: [PATCH 07/26] Add code generation tests. --- ...SBehaviorCodeGenerationIntegrationTests.js | 337 +++++++++++++++--- 1 file changed, 282 insertions(+), 55 deletions(-) diff --git a/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js index c6f8a5b45150..34383f529824 100644 --- a/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js @@ -10,31 +10,20 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function it('generates a working empty behavior', function () { // Create an empty behavior const project = new gd.ProjectHelper.createNewGDJSProject(); - const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); - const eventsBasedBehavior = new gd.EventsBasedBehavior(); - eventsBasedBehavior.setName('MyBehavior'); - eventsBasedBehavior.setFullName('My descriptive name'); - eventsBasedBehavior.setDescription('My description'); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); - const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( + const { behavior } = generatedBehavior( gd, project, eventsFunctionsExtension, - eventsBasedBehavior - ); - eventsBasedBehavior.delete(); - eventsFunctionsExtension.delete(); - project.delete(); - - // Instantiate the behavior - const { gdjs, runtimeScene } = makeMinimalGDJSMock(); - const CompiledRuntimeBehavior = makeCompiledRuntimeBehavior(gdjs); - const behaviorData = {}; - const ownerRuntimeObject = {}; - const behavior = new CompiledRuntimeBehavior( - runtimeScene, - behaviorData, - ownerRuntimeObject + eventsBasedBehavior, + { logCode: false } ); // Check that doStepPreEvents is always defined @@ -44,11 +33,13 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function it('generates a working behavior with doStepPreEvents using "Trigger Once" condition', function () { // Create a new behavior with events in doStepPreEvents const project = new gd.ProjectHelper.createNewGDJSProject(); - const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); - const eventsBasedBehavior = new gd.EventsBasedBehavior(); - eventsBasedBehavior.setName('MyBehavior'); - eventsBasedBehavior.setFullName('My descriptive name'); - eventsBasedBehavior.setDescription('My description'); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); const eventsSerializerElement = gd.Serializer.fromJSObject([ { @@ -74,6 +65,10 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function .insertNewEventsFunction('doStepPreEvents', 0) .getEvents() .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); // Instantiate the behavior twice const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( @@ -82,8 +77,6 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function eventsFunctionsExtension, eventsBasedBehavior ); - eventsBasedBehavior.delete(); - eventsFunctionsExtension.delete(); project.delete(); const { gdjs, runtimeScene } = makeMinimalGDJSMock(); @@ -120,11 +113,13 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function it('generates working behavior with properties (shared or not, with different types), all used in an expression', function () { // Create a new behavior with events in doStepPreEvents const project = new gd.ProjectHelper.createNewGDJSProject(); - const eventsFunctionsExtension = new gd.EventsFunctionsExtension(); - const eventsBasedBehavior = new gd.EventsBasedBehavior(); - eventsBasedBehavior.setName('MyBehavior'); - eventsBasedBehavior.setFullName('My descriptive name'); - eventsBasedBehavior.setDescription('My description'); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); // Set up some properties. eventsBasedBehavior @@ -177,35 +172,18 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function .insertNewEventsFunction('doStepPreEvents', 0) .getEvents() .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); - // Instantiate the behavior - const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( + const { runtimeScene, behavior } = generatedBehavior( gd, project, eventsFunctionsExtension, eventsBasedBehavior, { logCode: false } ); - eventsBasedBehavior.delete(); - eventsFunctionsExtension.delete(); - project.delete(); - - const { gdjs, runtimeScene } = makeMinimalGDJSMock(); - const CompiledRuntimeBehavior = makeCompiledRuntimeBehavior(gdjs); - const behaviorData = { - name: 'MyBehavior', - type: 'MyBehaviorType', - }; - const ownerRuntimeObject = new gdjs.RuntimeObject(runtimeScene, { - name: 'MyObject', - type: '', - }); - const behavior = new CompiledRuntimeBehavior( - runtimeScene, - behaviorData, - ownerRuntimeObject - ); - ownerRuntimeObject.addBehavior(behavior); // Check the default values are set. expect(behavior._getMyProperty()).toBe(true); @@ -224,8 +202,257 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function runtimeScene.getVariables().get('SuccessStringVariable').getAsString() ).toBe('true2Test'); }); + + it('Can use a property in a variable action', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + eventsBasedBehavior + .getPropertyDescriptors() + .insertNew('MyProperty', 0) + .setValue('123') + .setType('Number'); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyProperty', '=', '456'], + }, + ], + }, + ]); + eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect(behavior._getMyProperty()).toBe(123); + + behavior.MyFunction(); + expect(behavior._getMyProperty()).toBe(456); + }); + + it('Can use a property in a variable condition', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const scene = project.insertNewLayout('MyScene', 0); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + eventsBasedBehavior + .getPropertyDescriptors() + .insertNew('MyProperty', 0) + .setValue('123') + .setType('Number'); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyProperty', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + behavior.MyFunction(); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a parameter in a variable condition', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyParameter', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyParameter', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + behavior.MyFunction(123); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); }); +function generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + options = {} +) { + const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + options + ); + + const serializedProjectElement = new gd.SerializerElement(); + project.serializeTo(serializedProjectElement); + const serializedSceneElement = new gd.SerializerElement(); + const scene = project.insertNewLayout('MyScene', 0); + scene.serializeTo(serializedSceneElement); + const { gdjs, runtimeScene } = makeMinimalGDJSMock({ + gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)), + sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)), + }); + serializedProjectElement.delete(); + serializedSceneElement.delete(); + project.delete(); + + const CompiledRuntimeBehavior = makeCompiledRuntimeBehavior(gdjs); + const behaviorData = { + name: 'MyBehavior', + type: 'MyBehaviorType', + }; + const ownerRuntimeObject = new gdjs.RuntimeObject(runtimeScene, { + name: 'MyObject', + type: '', + }); + const behavior = new CompiledRuntimeBehavior( + runtimeScene, + behaviorData, + ownerRuntimeObject + ); + ownerRuntimeObject.addBehavior(behavior); + + return { gdjs, runtimeScene, behavior }; +} + function generateCompiledRuntimeBehaviorMaker( gd, project, From 94599bef9901e387f31dcfe4a73f840ba7d60d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 17 Nov 2024 16:10:25 +0100 Subject: [PATCH 08/26] Add test with name collisions. --- .../Project/VariablesContainersList.cpp | 16 +- .../TestUtils/CodeGenerationHelpers.js | 32 +- ...SBehaviorCodeGenerationIntegrationTests.js | 349 +++++++++--- ...omObjectsCodeGenerationIntegrationTests.js | 504 +++++++++++++++++- 4 files changed, 823 insertions(+), 78 deletions(-) diff --git a/Core/GDCore/Project/VariablesContainersList.cpp b/Core/GDCore/Project/VariablesContainersList.cpp index 9cac7a378f01..8833c9cc9977 100644 --- a/Core/GDCore/Project/VariablesContainersList.cpp +++ b/Core/GDCore/Project/VariablesContainersList.cpp @@ -71,15 +71,15 @@ VariablesContainersList VariablesContainersList:: variablesContainersList.Push(extension.GetGlobalVariables()); variablesContainersList.Push(extension.GetSceneVariables()); + gd::EventsFunctionTools::PropertiesToVariablesContainer( + eventsBasedBehavior.GetPropertyDescriptors(), propertyVariablesContainer); + variablesContainersList.Push(propertyVariablesContainer); + gd::EventsFunctionTools::ParametersToVariablesContainer( eventsFunction.GetParametersForEvents(extension), parameterVariablesContainer); variablesContainersList.Push(parameterVariablesContainer); - gd::EventsFunctionTools::PropertiesToVariablesContainer( - eventsBasedBehavior.GetPropertyDescriptors(), propertyVariablesContainer); - variablesContainersList.Push(propertyVariablesContainer); - variablesContainersList.firstLocalVariableContainerIndex = 4; return variablesContainersList; } @@ -95,15 +95,15 @@ VariablesContainersList::MakeNewVariablesContainersListForObjectEventsFunction( variablesContainersList.Push(extension.GetGlobalVariables()); variablesContainersList.Push(extension.GetSceneVariables()); + gd::EventsFunctionTools::PropertiesToVariablesContainer( + eventsBasedObject.GetPropertyDescriptors(), propertyVariablesContainer); + variablesContainersList.Push(propertyVariablesContainer); + gd::EventsFunctionTools::ParametersToVariablesContainer( eventsFunction.GetParametersForEvents(extension), parameterVariablesContainer); variablesContainersList.Push(parameterVariablesContainer); - gd::EventsFunctionTools::PropertiesToVariablesContainer( - eventsBasedObject.GetPropertyDescriptors(), propertyVariablesContainer); - variablesContainersList.Push(propertyVariablesContainer); - variablesContainersList.firstLocalVariableContainerIndex = 4; return variablesContainersList; } diff --git a/GDevelop.js/TestUtils/CodeGenerationHelpers.js b/GDevelop.js/TestUtils/CodeGenerationHelpers.js index 2ce31deacd12..e8963d014ff1 100644 --- a/GDevelop.js/TestUtils/CodeGenerationHelpers.js +++ b/GDevelop.js/TestUtils/CodeGenerationHelpers.js @@ -94,12 +94,22 @@ const generatedEventsCodeToJSFunction = (code, gdjs, 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 + gdjs, + options = {} ) { const includeFiles = new gd.SetString(); const codeNamespace = 'behaviorNamespace'; @@ -129,6 +139,9 @@ function generateCompiledEventsForEventsBasedBehavior( includeFiles, true ); + if (options.logCode) { + console.log(code); + } // Create a function returning the generated behavior. const compiledBehavior = new Function( @@ -146,12 +159,22 @@ function generateCompiledEventsForEventsBasedBehavior( 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 + gdjs, + options = {} ) { const includeFiles = new gd.SetString(); const codeNamespace = 'objectNamespace'; @@ -177,6 +200,9 @@ function generateCompiledEventsForEventsBasedObject( includeFiles, true ); + if (options.logCode) { + console.log(code); + } objectCodeGenerator.delete(); includeFiles.delete(); @@ -435,5 +461,7 @@ module.exports = { generateCompiledEventsFromSerializedEvents, generateCompiledEventsFunctionFromSerializedEvents, generateCompiledEventsForSerializedEventsBasedExtension, + generateCompiledEventsForEventsBasedBehavior, + generateCompiledEventsForEventsBasedObject, generateCompiledEventsForLayout, }; diff --git a/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js index 34383f529824..e957c5c12fea 100644 --- a/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSBehaviorCodeGenerationIntegrationTests.js @@ -1,5 +1,8 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks'); +const { + generateCompiledEventsForEventsBasedBehavior, +} = require('../TestUtils/CodeGenerationHelpers.js'); describe('libGD.js - GDJS Behavior Code Generation integration tests', function () { let gd = null; @@ -70,19 +73,20 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function eventsBasedBehavior ); - // Instantiate the behavior twice - const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( + const { gdjs, runtimeScene } = makeMinimalGDJSMock(); + const CompiledRuntimeBehavior = generateCompiledEventsForEventsBasedBehavior( gd, project, eventsFunctionsExtension, - eventsBasedBehavior + eventsBasedBehavior, + gdjs, + {logCode: false} ); project.delete(); - const { gdjs, runtimeScene } = makeMinimalGDJSMock(); - const CompiledRuntimeBehavior = makeCompiledRuntimeBehavior(gdjs); const behaviorData = {}; const ownerRuntimeObject = {}; + // Instantiate the behavior twice const behavior = new CompiledRuntimeBehavior( runtimeScene, behaviorData, @@ -330,6 +334,86 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function ).toBe(456); }); + it('Can use a property in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const scene = project.insertNewLayout('MyScene', 0); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + eventsBasedBehavior + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('123') + .setType('Number'); + + // Extension scene variable with the same name as the property. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + behavior.MyFunction(); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + it('Can use a parameter in a variable condition', () => { const project = new gd.ProjectHelper.createNewGDJSProject(); const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( @@ -404,6 +488,190 @@ describe('libGD.js - GDJS Behavior Code Generation integration tests', function .getAsNumber() ).toBe(456); }); + + it('Can use a parameter in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + // Property with the same name as the parameter. + eventsBasedBehavior + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('111') + .setType('Number'); + + // Extension scene variable with the same name as the parameter. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + behavior.MyFunction(123); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a local variable in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedBehavior = eventsFunctionsExtension + .getEventsBasedBehaviors() + .insertNew('MyBehavior', 0); + + // Property with the same name as the local variable. + eventsBasedBehavior + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('111') + .setType('Number'); + + // Extension scene variable with the same name as the local variable. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [ + { + name: 'MyIdentifier', + type: 'number', + value: 123, + }, + ], + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedBehavior + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureBehaviorEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedBehavior + ); + // Parameter with the same name as the local variable. + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, behavior } = generatedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + behavior.MyFunction(333); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); }); function generatedBehavior( @@ -413,14 +681,6 @@ function generatedBehavior( eventsBasedBehavior, options = {} ) { - const makeCompiledRuntimeBehavior = generateCompiledRuntimeBehaviorMaker( - gd, - project, - eventsFunctionsExtension, - eventsBasedBehavior, - options - ); - const serializedProjectElement = new gd.SerializerElement(); project.serializeTo(serializedProjectElement); const serializedSceneElement = new gd.SerializerElement(); @@ -430,11 +690,19 @@ function generatedBehavior( gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)), sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)), }); + + const CompiledRuntimeBehavior = generateCompiledEventsForEventsBasedBehavior( + gd, + project, + eventsFunctionsExtension, + eventsBasedBehavior, + gdjs, + options + ); serializedProjectElement.delete(); serializedSceneElement.delete(); project.delete(); - const CompiledRuntimeBehavior = makeCompiledRuntimeBehavior(gdjs); const behaviorData = { name: 'MyBehavior', type: 'MyBehaviorType', @@ -452,56 +720,3 @@ function generatedBehavior( return { gdjs, runtimeScene, behavior }; } - -function generateCompiledRuntimeBehaviorMaker( - gd, - project, - eventsFunctionsExtension, - eventsBasedBehavior, - { logCode } = {} -) { - 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 (logCode) console.log(code); - - // Create a function returning the generated behavior. - const makeCompiledBehavior = new Function( - 'gdjs', - `let behaviorNamespace = {}; - let Hashtable = gdjs.Hashtable; -${code} -return behaviorNamespace.MyBehavior;` - ); - - includeFiles.delete(); - - return makeCompiledBehavior; -} diff --git a/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js index d2413f731007..e28d7441de46 100644 --- a/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSCustomObjectsCodeGenerationIntegrationTests.js @@ -1,6 +1,6 @@ const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); const { - generateCompiledEventsFromSerializedEvents, + generateCompiledEventsForEventsBasedObject, generateCompiledEventsForSerializedEventsBasedExtension, } = require('../TestUtils/CodeGenerationHelpers.js'); const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); @@ -64,4 +64,506 @@ describe('libGD.js - GDJS Custom Object Code Generation integration tests', func runtimeScene.getVariables().get('SuccessStringVariable').getAsString() ).toBe('16trueTestTest'); }); + + + + it('Can use a property in a variable action', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + eventsBasedObject + .getPropertyDescriptors() + .insertNew('MyProperty', 0) + .setValue('123') + .setType('Number'); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyProperty', '=', '456'], + }, + ], + }, + ]); + eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect(object._getMyProperty()).toBe(123); + + object.MyFunction(); + expect(object._getMyProperty()).toBe(456); + }); + + it('Can use a property in a variable condition', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const scene = project.insertNewLayout('MyScene', 0); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + eventsBasedObject + .getPropertyDescriptors() + .insertNew('MyProperty', 0) + .setValue('123') + .setType('Number'); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyProperty', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + object.MyFunction(); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a property in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const scene = project.insertNewLayout('MyScene', 0); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + eventsBasedObject + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('123') + .setType('Number'); + + // Extension scene variable with the same name as the property. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0) + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + object.MyFunction(); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a parameter in a variable condition', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyParameter', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyParameter', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + object.MyFunction(123); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a parameter in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + // Property with the same name as the parameter. + eventsBasedObject + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('111') + .setType('Number'); + + // Extension scene variable with the same name as the parameter. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + object.MyFunction(123); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a local variable in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + const eventsBasedObject = eventsFunctionsExtension + .getEventsBasedObjects() + .insertNew('MyCustomObject', 0); + + // Property with the same name as the local variable. + eventsBasedObject + .getPropertyDescriptors() + .insertNew('MyIdentifier', 0) + .setValue('111') + .setType('Number'); + + // Extension scene variable with the same name as the local variable. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [ + { + name: 'MyIdentifier', + type: 'number', + value: 123, + }, + ], + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsBasedObject + .getEventsFunctions() + .insertNewEventsFunction('MyFunction', 0); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + gd.WholeProjectRefactorer.ensureObjectEventsFunctionsProperParameters( + eventsFunctionsExtension, + eventsBasedObject + ); + // Parameter with the same name as the local variable. + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { runtimeScene, object } = generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + object.MyFunction(333); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); }); + +function generatedCustomObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + options = {} +) { + const serializedProjectElement = new gd.SerializerElement(); + project.serializeTo(serializedProjectElement); + const serializedSceneElement = new gd.SerializerElement(); + const scene = project.insertNewLayout('MyScene', 0); + scene.serializeTo(serializedSceneElement); + const { gdjs, runtimeScene } = makeMinimalGDJSMock({ + gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)), + sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)), + }); + + const CompiledRuntimeCustomObject = generateCompiledEventsForEventsBasedObject( + gd, + project, + eventsFunctionsExtension, + eventsBasedObject, + gdjs, + options + ); + serializedProjectElement.delete(); + serializedSceneElement.delete(); + project.delete(); + + const object = new CompiledRuntimeCustomObject(runtimeScene, { content: {} }); + + return { gdjs, runtimeScene, object }; +} From 94d6602ed69cebfebc487118974010cf24b2ab9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 18 Nov 2024 14:52:29 +0100 Subject: [PATCH 09/26] Fix variable type detection. --- .../IDE/Events/ExpressionVariablePathFinder.h | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Core/GDCore/IDE/Events/ExpressionVariablePathFinder.h b/Core/GDCore/IDE/Events/ExpressionVariablePathFinder.h index 8bc77fc1be56..36ed811a960d 100644 --- a/Core/GDCore/IDE/Events/ExpressionVariablePathFinder.h +++ b/Core/GDCore/IDE/Events/ExpressionVariablePathFinder.h @@ -223,12 +223,28 @@ class GD_CORE_API ExpressionVariablePathFinder } }, [&]() { - // Ignore properties here. - // There is no support for "children" of properties. + // This is a property. + if (parameterType != "objectvar" && + projectScopedContainers.GetVariablesContainersList().Has( + identifier)) { + variablesContainer = + &(projectScopedContainers.GetVariablesContainersList() + .GetVariablesContainerFromVariableName(identifier)); + variableName = identifier; + // There is no support for "children" of properties. + } }, [&]() { - // Ignore parameters here. - // There is no support for "children" of parameters. + // This is a parameter. + if (parameterType != "objectvar" && + projectScopedContainers.GetVariablesContainersList().Has( + identifier)) { + variablesContainer = + &(projectScopedContainers.GetVariablesContainersList() + .GetVariablesContainerFromVariableName(identifier)); + variableName = identifier; + // There is no support for "children" of parameters. + } }, [&]() { // Ignore unrecognised identifiers here. From 86320f8035fdbbea98bf81882d206c2dc2cb5b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 18 Nov 2024 18:13:45 +0100 Subject: [PATCH 10/26] Add a refactoring operation for property type change. --- Core/GDCore/IDE/ProjectBrowserHelper.cpp | 31 +++++++++++++--- Core/GDCore/IDE/ProjectBrowserHelper.h | 29 +++++++++++++++ Core/GDCore/IDE/WholeProjectRefactorer.cpp | 36 +++++++++++++++++++ Core/GDCore/IDE/WholeProjectRefactorer.h | 20 +++++++++++ GDevelop.js/Bindings/Bindings.idl | 10 ++++++ GDevelop.js/Bindings/Wrapper.cpp | 2 ++ GDevelop.js/types.d.ts | 2 ++ GDevelop.js/types/gdwholeprojectrefactorer.js | 2 ++ .../EventsBasedBehaviorEditorPanel.js | 4 +++ .../EventsBasedBehaviorPropertiesEditor.js | 5 +++ .../EventsBasedObjectEditorPanel.js | 3 ++ .../EventsBasedObjectPropertiesEditor.js | 5 +++ .../EventsFunctionsExtensionEditor/index.js | 16 +++++++++ .../EventsBasedBehaviorEditorPanel.stories.js | 2 ++ .../EventsBasedObjectEditorPanel.stories.js | 1 + 15 files changed, 164 insertions(+), 4 deletions(-) diff --git a/Core/GDCore/IDE/ProjectBrowserHelper.cpp b/Core/GDCore/IDE/ProjectBrowserHelper.cpp index d13f96e59aae..b8904c33c8be 100644 --- a/Core/GDCore/IDE/ProjectBrowserHelper.cpp +++ b/Core/GDCore/IDE/ProjectBrowserHelper.cpp @@ -211,6 +211,20 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedBehavior &eventsBasedBehavior, gd::ArbitraryEventsWorkerWithContext &worker) { + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); + gd::ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( + project, eventsFunctionsExtension, + eventsBasedBehavior, + propertyVariablesContainer, + worker); +} + +void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( + gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + gd::VariablesContainer &propertyVariablesContainer, + gd::ArbitraryEventsWorkerWithContext &worker) { auto &behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions(); for (auto &&eventsFunction : behaviorEventsFunctions.GetInternalVector()) { @@ -218,8 +232,6 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( gd::ObjectsContainer::SourceType::Function); gd::VariablesContainer parameterVariablesContainer( gd::VariablesContainer::SourceType::Parameters); - gd::VariablesContainer propertyVariablesContainer( - gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForBehaviorEventsFunction( project, eventsFunctionsExtension, eventsBasedBehavior, @@ -244,6 +256,19 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents( const gd::EventsFunctionsExtension &eventsFunctionsExtension, const gd::EventsBasedObject &eventsBasedObject, gd::ArbitraryEventsWorkerWithContext &worker) { + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); + gd::ProjectBrowserHelper::ExposeEventsBasedObjectEvents( + project, eventsFunctionsExtension, eventsBasedObject, + propertyVariablesContainer, worker); +} + +void ProjectBrowserHelper::ExposeEventsBasedObjectEvents( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedObject &eventsBasedObject, + gd::VariablesContainer &propertyVariablesContainer, + gd::ArbitraryEventsWorkerWithContext &worker) { auto &objectEventsFunctions = eventsBasedObject.GetEventsFunctions(); for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) { @@ -251,8 +276,6 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents( gd::ObjectsContainer::SourceType::Function); gd::VariablesContainer parameterVariablesContainer( gd::VariablesContainer::SourceType::Parameters); - gd::VariablesContainer propertyVariablesContainer( - gd::VariablesContainer::SourceType::Properties); auto projectScopedContainers = gd::ProjectScopedContainers:: MakeNewProjectScopedContainersForObjectEventsFunction( project, eventsFunctionsExtension, eventsBasedObject, diff --git a/Core/GDCore/IDE/ProjectBrowserHelper.h b/Core/GDCore/IDE/ProjectBrowserHelper.h index 4b8c3dc940b1..823f4da50611 100644 --- a/Core/GDCore/IDE/ProjectBrowserHelper.h +++ b/Core/GDCore/IDE/ProjectBrowserHelper.h @@ -19,6 +19,7 @@ class ArbitraryEventsFunctionsWorker; class ArbitraryObjectsWorker; class ArbitraryEventBasedBehaviorsWorker; class ArbitraryBehaviorSharedDataWorker; +class VariablesContainer; } // namespace gd namespace gd { @@ -127,6 +128,20 @@ class GD_CORE_API ProjectBrowserHelper { const gd::EventsBasedBehavior &eventsBasedBehavior, gd::ArbitraryEventsWorkerWithContext &worker); + /** + * \brief Call the specified worker on all events of the event-based + * behavior. + * + * This should be the preferred way to traverse all the events of an + * event-based behavior. + */ + static void ExposeEventsBasedBehaviorEvents( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + gd::VariablesContainer &propertyVariablesContainer, + gd::ArbitraryEventsWorkerWithContext &worker); + /** * \brief Call the specified worker on all events of the event-based * object. @@ -152,6 +167,20 @@ class GD_CORE_API ProjectBrowserHelper { const gd::EventsBasedObject &eventsBasedObject, gd::ArbitraryEventsWorkerWithContext &worker); + /** + * \brief Call the specified worker on all events of the event-based + * object. + * + * This should be the preferred way to traverse all the events of an + * event-based object. + */ + static void ExposeEventsBasedObjectEvents( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedObject &eventsBasedObject, + gd::VariablesContainer &propertyVariablesContainer, + gd::ArbitraryEventsWorkerWithContext &worker); + /** * \brief Call the specified worker on all ObjectContainers of the project * (global, layouts...) diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index 1a8088439769..dab3d0f2483d 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -1129,6 +1129,42 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty( gd::ProjectBrowserHelper::ExposeProjectEvents(project, conditionRenamer); } +void WholeProjectRefactorer::ChangeEventsBasedBehaviorPropertyType( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + const gd::String &propertyName) { + std::unordered_set typeChangedPropertyNames; + typeChangedPropertyNames.insert(propertyName); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); + gd::EventsVariableInstructionTypeSwitcher + eventsVariableInstructionTypeSwitcher(project.GetCurrentPlatform(), + typeChangedPropertyNames, + propertyVariablesContainer); + gd::ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents( + project, eventsFunctionsExtension, eventsBasedBehavior, + propertyVariablesContainer, eventsVariableInstructionTypeSwitcher); +} + +void WholeProjectRefactorer::ChangeEventsBasedObjectPropertyType( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedObject &eventsBasedObject, + const gd::String &propertyName) { + std::unordered_set typeChangedPropertyNames; + typeChangedPropertyNames.insert(propertyName); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); + gd::EventsVariableInstructionTypeSwitcher + eventsVariableInstructionTypeSwitcher(project.GetCurrentPlatform(), + typeChangedPropertyNames, + propertyVariablesContainer); + gd::ProjectBrowserHelper::ExposeEventsBasedObjectEvents( + project, eventsFunctionsExtension, eventsBasedObject, + propertyVariablesContainer, eventsVariableInstructionTypeSwitcher); +} + void WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors( gd::Project &project, gd::Object &object, const gd::String &behaviorType, const gd::String &behaviorName) { diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index 0cc21b020c93..c4ed471742ae 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -268,6 +268,26 @@ class GD_CORE_API WholeProjectRefactorer { const gd::String& oldPropertyName, const gd::String& newPropertyName); + /** + * \brief Refactor the project **after** a property of a behavior has + * changed of type. + */ + static void ChangeEventsBasedBehaviorPropertyType( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedBehavior &eventsBasedBehavior, + const gd::String &propertyName); + + /** + * \brief Refactor the project **after** a property of an object has + * changed of type. + */ + static void ChangeEventsBasedObjectPropertyType( + gd::Project &project, + const gd::EventsFunctionsExtension &eventsFunctionsExtension, + const gd::EventsBasedObject &eventsBasedObject, + const gd::String &propertyName); + /** * \brief Add a behavior to an object and add required behaviors if necessary * to fill every behavior properties of the added behaviors. diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 67541c30ce8e..63a80492d44b 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2555,12 +2555,22 @@ interface WholeProjectRefactorer { [Const, Ref] EventsBasedBehavior eventsBasedBehavior, [Const] DOMString oldName, [Const] DOMString newName); + void STATIC_ChangeEventsBasedBehaviorPropertyType( + [Ref] Project project, + [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, + [Const, Ref] EventsBasedBehavior eventsBasedBehavior, + [Const] DOMString propertyName); void STATIC_RenameEventsBasedObjectProperty( [Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedObject eventsBasedObject, [Const] DOMString oldName, [Const] DOMString newName); + void STATIC_ChangeEventsBasedObjectPropertyType( + [Ref] Project project, + [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, + [Const, Ref] EventsBasedObject eventsBasedObject, + [Const] DOMString propertyName); void STATIC_RenameEventsBasedBehavior( [Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index 4641cbbc2333..4a06570ce9c6 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -737,6 +737,8 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_RenameEventsBasedBehaviorSharedProperty \ RenameEventsBasedBehaviorSharedProperty #define STATIC_RenameEventsBasedObjectProperty RenameEventsBasedObjectProperty +#define STATIC_ChangeEventsBasedBehaviorPropertyType ChangeEventsBasedBehaviorPropertyType +#define STATIC_ChangeEventsBasedObjectPropertyType ChangeEventsBasedObjectPropertyType #define STATIC_RenameEventsBasedBehavior RenameEventsBasedBehavior #define STATIC_UpdateBehaviorNameInEventsBasedBehavior UpdateBehaviorNameInEventsBasedBehavior #define STATIC_RenameEventsBasedObject RenameEventsBasedObject diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 71d151565501..15175d301010 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1911,7 +1911,9 @@ export class WholeProjectRefactorer extends EmscriptenObject { static moveObjectEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; static renameEventsBasedBehaviorProperty(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, oldName: string, newName: string): void; static renameEventsBasedBehaviorSharedProperty(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, oldName: string, newName: string): void; + static changeEventsBasedBehaviorPropertyType(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, propertyName: string): void; static renameEventsBasedObjectProperty(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, oldName: string, newName: string): void; + static changeEventsBasedObjectPropertyType(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, propertyName: string): void; static renameEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void; static updateBehaviorNameInEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, sourceBehaviorName: string): void; static renameEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void; diff --git a/GDevelop.js/types/gdwholeprojectrefactorer.js b/GDevelop.js/types/gdwholeprojectrefactorer.js index 5830ed55a40a..346f2b793bd4 100644 --- a/GDevelop.js/types/gdwholeprojectrefactorer.js +++ b/GDevelop.js/types/gdwholeprojectrefactorer.js @@ -14,7 +14,9 @@ declare class gdWholeProjectRefactorer { static moveObjectEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; static renameEventsBasedBehaviorProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void; static renameEventsBasedBehaviorSharedProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void; + static changeEventsBasedBehaviorPropertyType(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, propertyName: string): void; static renameEventsBasedObjectProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string): void; + static changeEventsBasedObjectPropertyType(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, propertyName: string): void; static renameEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void; static updateBehaviorNameInEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, sourceBehaviorName: string): void; static renameEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void; diff --git a/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.js b/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.js index 4ed5d27790e3..2f9d50f0b201 100644 --- a/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.js +++ b/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.js @@ -19,6 +19,7 @@ type Props = {| eventsBasedBehavior: gdEventsBasedBehavior, onRenameProperty: (oldName: string, newName: string) => void, onRenameSharedProperty: (oldName: string, newName: string) => void, + onPropertyTypeChanged: (propertyName: string) => void, onEventsFunctionsAdded: () => void, unsavedChanges?: ?UnsavedChanges, onConfigurationUpdated?: (?ExtensionItemConfigurationAttribute) => void, @@ -31,6 +32,7 @@ export default function EventsBasedBehaviorEditorPanel({ projectScopedContainersAccessor, onRenameProperty, onRenameSharedProperty, + onPropertyTypeChanged, unsavedChanges, onEventsFunctionsAdded, onConfigurationUpdated, @@ -90,6 +92,7 @@ export default function EventsBasedBehaviorEditorPanel({ onRenameProperty={onRenameProperty} behaviorObjectType={eventsBasedBehavior.getObjectType()} onPropertiesUpdated={onPropertiesUpdated} + onPropertyTypeChanged={onPropertyTypeChanged} onEventsFunctionsAdded={onEventsFunctionsAdded} /> )} @@ -103,6 +106,7 @@ export default function EventsBasedBehaviorEditorPanel({ properties={eventsBasedBehavior.getSharedPropertyDescriptors()} onRenameProperty={onRenameSharedProperty} onPropertiesUpdated={onPropertiesUpdated} + onPropertyTypeChanged={onPropertyTypeChanged} onEventsFunctionsAdded={onEventsFunctionsAdded} /> )} diff --git a/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorPropertiesEditor.js b/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorPropertiesEditor.js index 7a3cee803506..d48c2b0f14b6 100644 --- a/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorPropertiesEditor.js +++ b/newIDE/app/src/EventsBasedBehaviorEditor/EventsBasedBehaviorPropertiesEditor.js @@ -97,6 +97,7 @@ type Props = {| isSceneProperties?: boolean, onPropertiesUpdated?: () => void, onRenameProperty: (oldName: string, newName: string) => void, + onPropertyTypeChanged: (propertyName: string) => void, onEventsFunctionsAdded: () => void, behaviorObjectType?: string, |}; @@ -136,6 +137,7 @@ export default function EventsBasedBehaviorPropertiesEditor({ isSceneProperties, onPropertiesUpdated, onRenameProperty, + onPropertyTypeChanged, onEventsFunctionsAdded, behaviorObjectType, }: Props) { @@ -709,6 +711,9 @@ export default function EventsBasedBehaviorPropertiesEditor({ ); } forceUpdate(); + onPropertyTypeChanged( + property.getName() + ); onPropertiesUpdated && onPropertiesUpdated(); }} diff --git a/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.js b/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.js index 5c9fc46e1851..f236671075eb 100644 --- a/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.js +++ b/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.js @@ -17,6 +17,7 @@ type Props = {| eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, onRenameProperty: (oldName: string, newName: string) => void, + onPropertyTypeChanged: (propertyName: string) => void, onEventsFunctionsAdded: () => void, onOpenCustomObjectEditor: () => void, unsavedChanges?: ?UnsavedChanges, @@ -29,6 +30,7 @@ export default function EventsBasedObjectEditorPanel({ eventsFunctionsExtension, eventsBasedObject, onRenameProperty, + onPropertyTypeChanged, onEventsFunctionsAdded, onOpenCustomObjectEditor, unsavedChanges, @@ -84,6 +86,7 @@ export default function EventsBasedObjectEditorPanel({ eventsBasedObject={eventsBasedObject} onRenameProperty={onRenameProperty} onPropertiesUpdated={onPropertiesUpdated} + onPropertyTypeChanged={onPropertyTypeChanged} onEventsFunctionsAdded={onEventsFunctionsAdded} /> )} diff --git a/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectPropertiesEditor.js b/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectPropertiesEditor.js index 7dfae56fcc21..49fab3553768 100644 --- a/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectPropertiesEditor.js +++ b/newIDE/app/src/EventsBasedObjectEditor/EventsBasedObjectPropertiesEditor.js @@ -93,6 +93,7 @@ type Props = {| eventsBasedObject: gdEventsBasedObject, onPropertiesUpdated?: () => void, onRenameProperty: (oldName: string, newName: string) => void, + onPropertyTypeChanged: (propertyName: string) => void, onEventsFunctionsAdded: () => void, |}; @@ -129,6 +130,7 @@ export default function EventsBasedObjectPropertiesEditor({ eventsBasedObject, onPropertiesUpdated, onRenameProperty, + onPropertyTypeChanged, onEventsFunctionsAdded, }: Props) { const scrollView = React.useRef(null); @@ -700,6 +702,9 @@ export default function EventsBasedObjectPropertiesEditor({ ); } forceUpdate(); + onPropertyTypeChanged( + property.getName() + ); onPropertiesUpdated && onPropertiesUpdated(); }} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js index 37cc0abba169..77769928d87c 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js @@ -1438,6 +1438,14 @@ export default class EventsFunctionsExtensionEditor extends React.Component< newName ) } + onPropertyTypeChanged={propertyName => { + gd.WholeProjectRefactorer.changeEventsBasedBehaviorPropertyType( + project, + eventsFunctionsExtension, + selectedEventsBasedBehavior, + propertyName + ); + }} onEventsFunctionsAdded={() => { if (this.eventsFunctionList) { this.eventsFunctionList.forceUpdateList(); @@ -1462,6 +1470,14 @@ export default class EventsFunctionsExtensionEditor extends React.Component< newName ) } + onPropertyTypeChanged={propertyName => { + gd.WholeProjectRefactorer.changeEventsBasedObjectPropertyType( + project, + eventsFunctionsExtension, + selectedEventsBasedObject, + propertyName + ); + }} onEventsFunctionsAdded={() => { if (this.eventsFunctionList) { this.eventsFunctionList.forceUpdateList(); diff --git a/newIDE/app/src/stories/componentStories/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.stories.js b/newIDE/app/src/stories/componentStories/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.stories.js index 8cdd9e5c1891..414be7964d68 100644 --- a/newIDE/app/src/stories/componentStories/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsBasedBehaviorEditor/EventsBasedBehaviorEditorPanel.stories.js @@ -24,6 +24,7 @@ export const Default = () => ( eventsFunctionsExtension={testProject.testEventsFunctionsExtension} eventsBasedBehavior={testProject.testEventsBasedBehavior} onRenameProperty={action('property rename')} + onPropertyTypeChanged={action('onPropertyTypeChanged')} onRenameSharedProperty={action('shared property rename')} onEventsFunctionsAdded={action('functions added')} /> @@ -40,6 +41,7 @@ export const WithoutFunction = () => ( eventsFunctionsExtension={testProject.testEventsFunctionsExtension} eventsBasedBehavior={testProject.testEmptyEventsBasedBehavior} onRenameProperty={action('property rename')} + onPropertyTypeChanged={action('onPropertyTypeChanged')} onRenameSharedProperty={action('shared property rename')} onEventsFunctionsAdded={action('functions added')} /> diff --git a/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.stories.js b/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.stories.js index e22430398156..7963125b4d8d 100644 --- a/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsBasedObjectEditor/EventsBasedObjectEditorPanel.stories.js @@ -24,6 +24,7 @@ export const Default = () => ( eventsFunctionsExtension={testProject.testEventsFunctionsExtension} eventsBasedObject={testProject.testEventsBasedObject} onRenameProperty={action('property rename')} + onPropertyTypeChanged={action('onPropertyTypeChanged')} onEventsFunctionsAdded={action('functions added')} onOpenCustomObjectEditor={action('onOpenCustomObjectEditor')} onEventsBasedObjectChildrenEdited={action( From 4fa03dc1443acacb4e21ef77c36d21daed617eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 25 Nov 2024 14:01:25 +0100 Subject: [PATCH 11/26] Add tests for property renaming. --- Core/tests/DummyPlatform.cpp | 46 ++++- Core/tests/WholeProjectRefactorer.cpp | 256 ++++++++++++++++++++++++++ 2 files changed, 299 insertions(+), 3 deletions(-) diff --git a/Core/tests/DummyPlatform.cpp b/Core/tests/DummyPlatform.cpp index f11d34801a05..7986263e1fcf 100644 --- a/Core/tests/DummyPlatform.cpp +++ b/Core/tests/DummyPlatform.cpp @@ -263,6 +263,46 @@ void SetupProjectWithDummyPlatform(gd::Project& project, extension->SetExtensionInformation( "BuiltinVariables", "My testing extension for variables", "", "", ""); + extension + ->AddCondition("NumberVariable", + "Variable value", + "Compare the number value of a variable.", + "The variable _PARAM0_", + "", + "", + "") + .AddParameter("variableOrPropertyOrParameter", _("Variable")) + .UseStandardRelationalOperatorParameters( + "number", gd::ParameterOptions::MakeNewOptions()); + + extension + ->AddCondition("StringVariable", + "Variable value", + "Compare the text (string) of a variable.", + "The variable _PARAM0_", + "", + "", + "") + .AddParameter("variableOrPropertyOrParameter", _("Variable")) + .UseStandardRelationalOperatorParameters( + "string", gd::ParameterOptions::MakeNewOptions()); + + extension + ->AddCondition( + "BooleanVariable", + "Variable value", + "Compare the boolean value of a variable.", + "The variable _PARAM0_ is _PARAM1_", + "", + "", + "") + .AddParameter("variableOrPropertyOrParameter", _("Variable")) + .AddParameter("trueorfalse", _("Check if the value is")) + .SetDefaultValue("true") + // This parameter allows to keep the operand expression + // when the editor switch between variable instructions. + .AddCodeOnlyParameter("trueorfalse", ""); + extension ->AddAction("SetNumberVariable", "Change variable value", @@ -271,7 +311,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project, "", "", "") - .AddParameter("variable", "Variable") + .AddParameter("variableOrProperty", "Variable") .AddParameter("operator", "Operator", "number") .AddParameter("number", "Value") .SetFunctionName("setNumberVariable"); @@ -284,7 +324,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project, "", "", "") - .AddParameter("variable", "Variable") + .AddParameter("variableOrProperty", "Variable") .AddParameter("operator", "Operator", "string") .AddParameter("string", "Value") .SetFunctionName("setStringVariable"); @@ -297,7 +337,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project, "", "", "") - .AddParameter("variable", "Variable") + .AddParameter("variableOrProperty", "Variable") .AddParameter("operator", "Operator", "boolean") // This parameter allows to keep the operand expression // when the editor switch between variable instructions. diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 603a7c6e4f81..57e37a1837ab 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -105,6 +105,74 @@ CreateInstructionWithVariableParameter(gd::Project &project, return event.GetActions().Insert(instruction); } +const gd::Instruction & +CreateNumberVariableSetterAction(gd::Project &project, + gd::EventsList &events, + const gd::String &variableName, + const gd::String &expression) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("SetNumberVariable"); + instruction.SetParametersCount(3); + instruction.SetParameter(0, variableName); + instruction.SetParameter(1, "="); + instruction.SetParameter(2, expression); + return event.GetActions().Insert(instruction); +} + +const gd::Instruction & +CreateNumberVariableGetterCondition(gd::Project &project, + gd::EventsList &events, + const gd::String &variableName, + const gd::String &expression) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("NumberVariable"); + instruction.SetParametersCount(3); + instruction.SetParameter(0, variableName); + instruction.SetParameter(1, "="); + instruction.SetParameter(2, expression); + return event.GetConditions().Insert(instruction); +} + +const gd::Instruction & +CreateStringVariableSetterAction(gd::Project &project, + gd::EventsList &events, + const gd::String &variableName, + const gd::String &expression) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("SetStringVariable"); + instruction.SetParametersCount(3); + instruction.SetParameter(0, variableName); + instruction.SetParameter(1, "="); + instruction.SetParameter(2, expression); + return event.GetActions().Insert(instruction); +} + +const gd::Instruction & +CreateStringVariableGetterCondition(gd::Project &project, + gd::EventsList &events, + const gd::String &variableName, + const gd::String &expression) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("StringVariable"); + instruction.SetParametersCount(3); + instruction.SetParameter(0, variableName); + instruction.SetParameter(1, "="); + instruction.SetParameter(2, expression); + return event.GetConditions().Insert(instruction); +} + enum TestEvent { FreeFunctionAction, FreeFunctionWithExpression, @@ -3106,6 +3174,54 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedProperty])"); } + SECTION("(Events based behavior) property renamed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + auto &behaviorAction = + eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction( + "MyBehaviorEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters( + eventsExtension, eventsBasedBehavior); + auto &instruction = CreateNumberVariableSetterAction( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( + project, eventsExtension, eventsBasedBehavior, "MyProperty", + "MyRenamedProperty"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedProperty"); + } + + SECTION("(Events based behavior) property renamed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + auto &behaviorAction = + eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction( + "MyBehaviorEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters( + eventsExtension, eventsBasedBehavior); + auto &instruction = CreateNumberVariableGetterCondition( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::RenameEventsBasedBehaviorProperty( + project, eventsExtension, eventsBasedBehavior, "MyProperty", + "MyRenamedProperty"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedProperty"); + } + SECTION("(Events based behavior) property not renamed (in variable parameter)") { gd::Project project; gd::Platform platform; @@ -3137,6 +3253,52 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyProperty)"); } + SECTION("(Events based behavior) property type changed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + auto &behaviorAction = + eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction( + "MyBehaviorEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters( + eventsExtension, eventsBasedBehavior); + // The property was of type "string". + auto &instruction = CreateStringVariableSetterAction( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::ChangeEventsBasedBehaviorPropertyType( + project, eventsExtension, eventsBasedBehavior, "MyProperty"); + + REQUIRE(instruction.GetType() == "SetNumberVariable"); + } + + SECTION("(Events based behavior) property type changed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedBehavior = + eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior"); + + auto &behaviorAction = + eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction( + "MyBehaviorEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters( + eventsExtension, eventsBasedBehavior); + // The property was of type "string". + auto &instruction = CreateStringVariableGetterCondition( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::ChangeEventsBasedBehaviorPropertyType( + project, eventsExtension, eventsBasedBehavior, "MyProperty"); + + REQUIRE(instruction.GetType() == "NumberVariable"); + } + SECTION("(Events based behavior) shared property renamed") { gd::Project project; gd::Platform platform; @@ -3276,6 +3438,54 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedProperty])"); } + SECTION("(Events based object) property renamed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + auto &behaviorAction = + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyObjectEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + eventsExtension, eventsBasedObject); + auto &instruction = CreateNumberVariableSetterAction( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::RenameEventsBasedObjectProperty( + project, eventsExtension, eventsBasedObject, "MyProperty", + "MyRenamedProperty"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedProperty"); + } + + SECTION("(Events based object) property renamed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + auto &behaviorAction = + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyObjectEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + eventsExtension, eventsBasedObject); + auto &instruction = CreateNumberVariableGetterCondition( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::RenameEventsBasedObjectProperty( + project, eventsExtension, eventsBasedObject, "MyProperty", + "MyRenamedProperty"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedProperty"); + } + SECTION("(Events based object) property not renamed (in variable parameter)") { gd::Project project; gd::Platform platform; @@ -3306,6 +3516,52 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { REQUIRE(instruction2.GetParameter(0).GetPlainString() == "MyExtension::GetVariableAsNumber(MyProperty)"); } + + SECTION("(Events based object) property type changed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + auto &behaviorAction = + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyObjectEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + eventsExtension, eventsBasedObject); + // The property was of type "string". + auto &instruction = CreateStringVariableSetterAction( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::ChangeEventsBasedObjectPropertyType( + project, eventsExtension, eventsBasedObject, "MyProperty"); + + REQUIRE(instruction.GetType() == "SetNumberVariable"); + } + + SECTION("(Events based object) property type changed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + auto &eventsBasedObject = + eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject"); + + auto &behaviorAction = + eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction( + "MyObjectEventsFunction", 0); + gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters( + eventsExtension, eventsBasedObject); + // The property was of type "string". + auto &instruction = CreateStringVariableGetterCondition( + project, behaviorAction.GetEvents(), "MyProperty", "123"); + + gd::WholeProjectRefactorer::ChangeEventsBasedObjectPropertyType( + project, eventsExtension, eventsBasedObject, "MyProperty"); + + REQUIRE(instruction.GetType() == "NumberVariable"); + } } // TODO: Check that this works when behaviors are attached to a child-object. TEST_CASE("WholeProjectRefactorer (FindInvalidRequiredBehaviorProperties)", From 86c7469f6d053dca1211623be51a07d01c4231b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 24 Nov 2024 22:11:33 +0100 Subject: [PATCH 12/26] Fix property renaming. --- .../Extensions/Metadata/ValueTypeMetadata.h | 15 ++++++++++++++- Core/GDCore/IDE/Events/EventsPropertyReplacer.cpp | 6 +++--- Core/GDCore/Project/VariablesContainersList.cpp | 4 ++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h index 6b311fae10d4..3dde5c03a7cc 100644 --- a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h @@ -129,7 +129,7 @@ class GD_CORE_API ValueTypeMetadata { } /** - * \brief Return true if the type of the parameter is a number. + * \brief Return true if the type of the parameter is a variable. * \note If you had a new type of parameter, also add it in the IDE ( * see EventsFunctionParametersEditor, ParameterRenderingService * and ExpressionAutocompletion) and in the EventsCodeGenerator. @@ -138,6 +138,19 @@ class GD_CORE_API ValueTypeMetadata { return gd::ValueTypeMetadata::GetPrimitiveValueType(name) == "variable"; } + /** + * \brief Return true if the type of the parameter is a variable and not a + * property or a parameter. + */ + bool IsVariableOnly() const { + return + // Any variable. + name == "variable" || + // Old, "pre-scoped" variables: + name == "objectvar" || name == "globalvar" || + name == "scenevar"; + } + /** * \brief Return true if the type is a variable but from a specific scope * (scene, project or object). In new code, prefer to use the more generic "variable" diff --git a/Core/GDCore/IDE/Events/EventsPropertyReplacer.cpp b/Core/GDCore/IDE/Events/EventsPropertyReplacer.cpp index 2a88d290c4a9..bb462674ae2e 100644 --- a/Core/GDCore/IDE/Events/EventsPropertyReplacer.cpp +++ b/Core/GDCore/IDE/Events/EventsPropertyReplacer.cpp @@ -168,7 +168,7 @@ class GD_CORE_API ExpressionPropertyReplacer parameterMetadata->GetValueTypeMetadata(); if (gd::EventsPropertyReplacer::CanContainProperty( parameterTypeMetadata)) { - isParentTypeAVariable = parameterTypeMetadata.IsVariable(); + isParentTypeAVariable = parameterTypeMetadata.IsVariableOnly(); parameter->Visit(*this); } } @@ -231,7 +231,7 @@ bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction, if (node) { ExpressionPropertyReplacer renamer( platform, GetProjectScopedContainers(), targetPropertiesContainer, - parameterMetadata.GetValueTypeMetadata().IsVariable(), + parameterMetadata.GetValueTypeMetadata().IsVariableOnly(), oldToNewPropertyNames, removedPropertyNames); node->Visit(renamer); @@ -257,7 +257,7 @@ bool EventsPropertyReplacer::DoVisitEventExpression( if (node) { ExpressionPropertyReplacer renamer( platform, GetProjectScopedContainers(), targetPropertiesContainer, - metadata.GetValueTypeMetadata().IsVariable(), oldToNewPropertyNames, + metadata.GetValueTypeMetadata().IsVariableOnly(), oldToNewPropertyNames, removedPropertyNames); node->Visit(renamer); diff --git a/Core/GDCore/Project/VariablesContainersList.cpp b/Core/GDCore/Project/VariablesContainersList.cpp index 8833c9cc9977..3b07eeac160c 100644 --- a/Core/GDCore/Project/VariablesContainersList.cpp +++ b/Core/GDCore/Project/VariablesContainersList.cpp @@ -71,6 +71,10 @@ VariablesContainersList VariablesContainersList:: variablesContainersList.Push(extension.GetGlobalVariables()); variablesContainersList.Push(extension.GetSceneVariables()); + gd::EventsFunctionTools::PropertiesToVariablesContainer( + eventsBasedBehavior.GetSharedPropertyDescriptors(), propertyVariablesContainer); + variablesContainersList.Push(propertyVariablesContainer); + gd::EventsFunctionTools::PropertiesToVariablesContainer( eventsBasedBehavior.GetPropertyDescriptors(), propertyVariablesContainer); variablesContainersList.Push(propertyVariablesContainer); From 03b6dc2eaa04203ea2409ef3c673e1c49b9320d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 25 Nov 2024 18:59:20 +0100 Subject: [PATCH 13/26] WIP: Rename parameters --- .../IDE/Events/EventsParameterReplace.cpp | 251 ++++++++++++++++++ .../IDE/Events/EventsParameterReplacer.h | 55 ++++ .../IDE/Events/EventsPropertyReplacer.h | 1 + Core/GDCore/IDE/WholeProjectRefactorer.cpp | 42 +++ Core/GDCore/IDE/WholeProjectRefactorer.h | 7 + 5 files changed, 356 insertions(+) create mode 100644 Core/GDCore/IDE/Events/EventsParameterReplace.cpp create mode 100644 Core/GDCore/IDE/Events/EventsParameterReplacer.h diff --git a/Core/GDCore/IDE/Events/EventsParameterReplace.cpp b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp new file mode 100644 index 000000000000..c3b68d532c21 --- /dev/null +++ b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp @@ -0,0 +1,251 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/IDE/Events/EventsParameterReplacer.h" + +#include +#include +#include +#include +#include + +#include "GDCore/Events/Event.h" +#include "GDCore/Events/EventsList.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h" +#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Metadata/ParameterMetadata.h" +#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h" +#include "GDCore/IDE/Events/ExpressionValidator.h" +#include "GDCore/Project/Layout.h" +#include "GDCore/Project/Project.h" +#include "GDCore/Project/ProjectScopedContainers.h" +#include "GDCore/Project/PropertiesContainer.h" +#include "GDCore/String.h" +#include "GDCore/Tools/Log.h" + +namespace gd { + +/** + * \brief Go through the nodes and rename parameters, + * or signal if the instruction must be renamed if a removed property is used. + * + * \see gd::ExpressionParser2 + */ +class GD_CORE_API ExpressionParameterReplacer + : public ExpressionParser2NodeWorker { + public: + ExpressionParameterReplacer( + const gd::Platform& platform_, + const gd::ProjectScopedContainers& projectScopedContainers_, + const gd::ParameterMetadataContainer& targetParameterContainer_, + bool isParentTypeAVariable_, + const std::unordered_map& oldToNewPropertyNames_) + : hasDoneRenaming(false), + platform(platform_), + projectScopedContainers(projectScopedContainers_), + targetParameterContainer(targetParameterContainer_), + isParentTypeAVariable(isParentTypeAVariable_), + oldToNewPropertyNames(oldToNewPropertyNames_){}; + virtual ~ExpressionParameterReplacer(){}; + + bool HasDoneRenaming() const { return hasDoneRenaming; } + + protected: + void OnVisitSubExpressionNode(SubExpressionNode& node) override { + node.expression->Visit(*this); + } + void OnVisitOperatorNode(OperatorNode& node) override { + node.leftHandSide->Visit(*this); + node.rightHandSide->Visit(*this); + } + void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override { + node.factor->Visit(*this); + } + void OnVisitNumberNode(NumberNode& node) override {} + void OnVisitTextNode(TextNode& node) override {} + void OnVisitVariableNode(VariableNode& node) override { + if (isParentTypeAVariable) { + // Do nothing, it's a variable. + if (node.child) node.child->Visit(*this); + return; + } + + // The node represents a variable or an object name on which a variable + // will be accessed, or a property with a child. + + projectScopedContainers.MatchIdentifierWithName( + // The property name is changed after the refactor operation. + node.name, + [&]() { + // Do nothing, it's an object variable. + if (node.child) node.child->Visit(*this); + }, [&]() { + // Do nothing, it's a variable. + if (node.child) node.child->Visit(*this); + }, [&]() { + // Do nothing, it's a property. + if (node.child) node.child->Visit(*this); + }, [&]() { + // This is a parameter + RenameParameter(node.name); + if (node.child) node.child->Visit(*this); + }, [&]() { + // Do nothing, it's something else. + if (node.child) node.child->Visit(*this); + }); + } + void OnVisitVariableAccessorNode(VariableAccessorNode& node) override { + if (node.child) node.child->Visit(*this); + } + void OnVisitVariableBracketAccessorNode( + VariableBracketAccessorNode& node) override { + bool isGrandParentTypeAVariable = isParentTypeAVariable; + isParentTypeAVariable = false; + node.expression->Visit(*this); + isParentTypeAVariable = isGrandParentTypeAVariable; + if (node.child) node.child->Visit(*this); + } + void OnVisitIdentifierNode(IdentifierNode& node) override { + if (isParentTypeAVariable) { + // Do nothing, it's a variable. + return; + } + + auto& propertiesContainersList = + projectScopedContainers.GetPropertiesContainersList(); + + projectScopedContainers.MatchIdentifierWithName( + // The property name is changed after the refactor operation + node.identifierName, + [&]() { + // Do nothing, it's an object variable. + }, [&]() { + // Do nothing, it's a variable. + }, [&]() { + // Do nothing, it's a property. + }, [&]() { + // This is a parameter. + RenameParameter(node.identifierName); + }, [&]() { + // Do nothing, it's something else. + }); + } + void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {} + void OnVisitFunctionCallNode(FunctionCallNode &node) override { + bool isGrandParentTypeAVariable = isParentTypeAVariable; + for (auto ¶meter : node.parameters) { + const auto ¶meterMetadata = + gd::MetadataProvider::GetFunctionCallParameterMetadata( + platform, projectScopedContainers.GetObjectsContainersList(), + node, *parameter); + if (!parameterMetadata) { + continue; + } + const auto ¶meterTypeMetadata = + parameterMetadata->GetValueTypeMetadata(); + if (gd::EventsParameterReplacer::CanContainProperty( + parameterTypeMetadata)) { + isParentTypeAVariable = parameterTypeMetadata.IsVariableOnly(); + parameter->Visit(*this); + } + } + isParentTypeAVariable = isGrandParentTypeAVariable; + } + void OnVisitEmptyNode(EmptyNode& node) override {} + + private: + bool hasDoneRenaming; + + bool RenameParameter( + gd::String& name) { + if (oldToNewPropertyNames.count(name) >= 1) { + name = oldToNewPropertyNames.find(name)->second; + hasDoneRenaming = true; + return true; + } + + return false; // Nothing was changed or done. + } + + // Scope: + const gd::Platform& platform; + const gd::ProjectScopedContainers& projectScopedContainers; + + // Renaming or removing to do: + const gd::ParameterMetadataContainer& targetParameterContainer; + const std::unordered_map& oldToNewPropertyNames; + + gd::String objectNameToUseForVariableAccessor; + bool isParentTypeAVariable; +}; + +bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction, + bool isCondition) { + const auto& metadata = isCondition + ? gd::MetadataProvider::GetConditionMetadata( + platform, instruction.GetType()) + : gd::MetadataProvider::GetActionMetadata( + platform, instruction.GetType()); + + gd::ParameterMetadataTools::IterateOverParametersWithIndex( + instruction.GetParameters(), + metadata.GetParameters(), + [&](const gd::ParameterMetadata& parameterMetadata, + const gd::Expression& parameterValue, + size_t parameterIndex, + const gd::String& lastObjectName) { + if (!gd::EventsParameterReplacer::CanContainProperty( + parameterMetadata.GetValueTypeMetadata())) { + return; + } + auto node = parameterValue.GetRootNode(); + if (node) { + ExpressionParameterReplacer renamer( + platform, GetProjectScopedContainers(), targetParameterContainer, + parameterMetadata.GetValueTypeMetadata().IsVariableOnly(), + oldToNewPropertyNames); + node->Visit(renamer); + + if (renamer.HasDoneRenaming()) { + instruction.SetParameter( + parameterIndex, ExpressionParser2NodePrinter::PrintNode(*node)); + } + } + }); + + return false; +} + +bool EventsParameterReplacer::DoVisitEventExpression( + gd::Expression& expression, const gd::ParameterMetadata& metadata) { + if (!gd::EventsParameterReplacer::CanContainProperty( + metadata.GetValueTypeMetadata())) { + return false; + } + auto node = expression.GetRootNode(); + if (node) { + ExpressionParameterReplacer renamer( + platform, GetProjectScopedContainers(), targetParameterContainer, + metadata.GetValueTypeMetadata().IsVariableOnly(), oldToNewPropertyNames); + node->Visit(renamer); + + if (renamer.HasDoneRenaming()) { + expression = ExpressionParser2NodePrinter::PrintNode(*node); + } + } + + return false; +} + +bool EventsParameterReplacer::CanContainProperty( + const gd::ValueTypeMetadata &valueTypeMetadata) { + return valueTypeMetadata.IsVariable() || valueTypeMetadata.IsNumber() || + valueTypeMetadata.IsString(); +} + +EventsParameterReplacer::~EventsParameterReplacer() {} + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/EventsParameterReplacer.h b/Core/GDCore/IDE/Events/EventsParameterReplacer.h new file mode 100644 index 000000000000..effc5e7d97e5 --- /dev/null +++ b/Core/GDCore/IDE/Events/EventsParameterReplacer.h @@ -0,0 +1,55 @@ +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "GDCore/IDE/Events/ArbitraryEventsWorker.h" +#include "GDCore/String.h" +namespace gd { +class BaseEvent; +class PropertiesContainer; +class EventsList; +class Platform; +} // namespace gd + +namespace gd { +/** + * \brief Replace in expressions and in parameters of actions or conditions, + * references to the name of a property by another. + * + * \ingroup IDE + */ +class GD_CORE_API EventsParameterReplacer + : public ArbitraryEventsWorkerWithContext { + public: + EventsParameterReplacer( + const gd::Platform &platform_, + const gd::ParameterMetadataContainer &targetParameterContainer_, + const std::unordered_map &oldToNewPropertyNames_) + : platform(platform_), + targetParameterContainer(targetParameterContainer_), + oldToNewPropertyNames(oldToNewPropertyNames_){}; + virtual ~EventsParameterReplacer(); + + static bool CanContainProperty(const gd::ValueTypeMetadata &valueTypeMetadata); + + private: + bool DoVisitInstruction(gd::Instruction &instruction, + bool isCondition) override; + bool DoVisitEventExpression(gd::Expression &expression, + const gd::ParameterMetadata &metadata) override; + + const gd::Platform &platform; + const gd::ParameterMetadataContainer &targetParameterContainer; + const std::unordered_map &oldToNewPropertyNames; +}; + +} // namespace gd diff --git a/Core/GDCore/IDE/Events/EventsPropertyReplacer.h b/Core/GDCore/IDE/Events/EventsPropertyReplacer.h index 4a362b93bd10..5c850230a8a7 100644 --- a/Core/GDCore/IDE/Events/EventsPropertyReplacer.h +++ b/Core/GDCore/IDE/Events/EventsPropertyReplacer.h @@ -4,6 +4,7 @@ * reserved. This project is released under the MIT License. */ #pragma once + #include #include #include diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index dab3d0f2483d..3a975c3b7d62 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -19,6 +19,7 @@ #include "GDCore/IDE/Events/BehaviorTypeRenamer.h" #include "GDCore/IDE/Events/CustomObjectTypeRenamer.h" #include "GDCore/IDE/Events/EventsBehaviorRenamer.h" +#include "GDCore/IDE/Events/EventsParameterReplacer.h" #include "GDCore/IDE/Events/EventsPropertyReplacer.h" #include "GDCore/IDE/Events/EventsRefactorer.h" #include "GDCore/IDE/Events/EventsVariableInstructionTypeSwitcher.h" @@ -816,6 +817,47 @@ void WholeProjectRefactorer::RenameObjectEventsFunction( } } +void WholeProjectRefactorer::RenameParameter( + gd::Project &project, gd::ProjectScopedContainers &projectScopedContainers, + gd::EventsFunction &eventsFunction, const gd::String &oldParameterName, + const gd::String &newParameterName) { + auto ¶meters = eventsFunction.GetParameters(); + if (!parameters.HasParameterNamed(oldParameterName)) + return; + auto ¶meter = parameters.GetParameter(oldParameterName); + if (parameter.GetType() == "Object") { + gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( + project, projectScopedContainers, eventsFunction, oldParameterName, + newParameterName, false); + } else if (parameter.GetType() == "Behavior") { + size_t behaviorParameterIndex = parameters.GetParameterPosition(parameter); + size_t objectParameterIndex = + gd::ParameterMetadataTools::GetObjectParameterIndexFor( + parameters, behaviorParameterIndex); + const gd::String &objectName = + parameters.GetParameter(objectParameterIndex).GetName(); + gd::EventsBehaviorRenamer behaviorRenamer(project.GetCurrentPlatform(), + objectName, oldParameterName, + newParameterName); + behaviorRenamer.Launch(eventsFunction.GetEvents(), projectScopedContainers); + } else { + // Rename parameter names directly used as an identifier. + std::unordered_map oldToNewParameterNames = { + {oldParameterName, newParameterName}}; + gd::EventsParameterReplacer eventsParameterReplacer( + project.GetCurrentPlatform(), parameters, oldToNewParameterNames); + eventsParameterReplacer.Launch(eventsFunction.GetEvents(), + projectScopedContainers); + + // Rename parameter names in legacy expressions and instructions + gd::ProjectElementRenamer projectElementRenamer( + project.GetCurrentPlatform(), "functionParameterName", oldParameterName, + newParameterName); + projectElementRenamer.Launch(eventsFunction.GetEvents(), + projectScopedContainers); + } +} + void WholeProjectRefactorer::MoveEventsFunctionParameter( gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index c4ed471742ae..8a0d0c573648 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -176,6 +176,13 @@ class GD_CORE_API WholeProjectRefactorer { const gd::String& oldFunctionName, const gd::String& newFunctionName); + static void + RenameParameter(gd::Project &project, + gd::ProjectScopedContainers &projectScopedContainers, + gd::EventsFunction &eventsFunction, + const gd::String &oldParameterName, + const gd::String &newParameterName); + /** * \brief Refactor the project **before** an events function parameter * is moved. From 26f4c2d17a610d9e266ffbbf4a3d50e1ddf9f836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 30 Nov 2024 17:46:12 +0100 Subject: [PATCH 14/26] Fix object parameter renaming. --- .../IDE/Events/EventsParameterReplace.cpp | 21 +-- .../IDE/Events/EventsParameterReplacer.h | 5 +- Core/GDCore/IDE/Events/EventsRefactorer.cpp | 3 +- Core/GDCore/IDE/WholeProjectRefactorer.cpp | 9 +- Core/tests/DummyPlatform.cpp | 4 +- Core/tests/WholeProjectRefactorer.cpp | 154 ++++++++++++++++++ 6 files changed, 171 insertions(+), 25 deletions(-) diff --git a/Core/GDCore/IDE/Events/EventsParameterReplace.cpp b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp index c3b68d532c21..8d1150ea986b 100644 --- a/Core/GDCore/IDE/Events/EventsParameterReplace.cpp +++ b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp @@ -40,13 +40,11 @@ class GD_CORE_API ExpressionParameterReplacer ExpressionParameterReplacer( const gd::Platform& platform_, const gd::ProjectScopedContainers& projectScopedContainers_, - const gd::ParameterMetadataContainer& targetParameterContainer_, bool isParentTypeAVariable_, const std::unordered_map& oldToNewPropertyNames_) : hasDoneRenaming(false), platform(platform_), projectScopedContainers(projectScopedContainers_), - targetParameterContainer(targetParameterContainer_), isParentTypeAVariable(isParentTypeAVariable_), oldToNewPropertyNames(oldToNewPropertyNames_){}; virtual ~ExpressionParameterReplacer(){}; @@ -113,10 +111,6 @@ class GD_CORE_API ExpressionParameterReplacer // Do nothing, it's a variable. return; } - - auto& propertiesContainersList = - projectScopedContainers.GetPropertiesContainersList(); - projectScopedContainers.MatchIdentifierWithName( // The property name is changed after the refactor operation node.identifierName, @@ -146,7 +140,7 @@ class GD_CORE_API ExpressionParameterReplacer } const auto ¶meterTypeMetadata = parameterMetadata->GetValueTypeMetadata(); - if (gd::EventsParameterReplacer::CanContainProperty( + if (gd::EventsParameterReplacer::CanContainParameter( parameterTypeMetadata)) { isParentTypeAVariable = parameterTypeMetadata.IsVariableOnly(); parameter->Visit(*this); @@ -174,8 +168,7 @@ class GD_CORE_API ExpressionParameterReplacer const gd::Platform& platform; const gd::ProjectScopedContainers& projectScopedContainers; - // Renaming or removing to do: - const gd::ParameterMetadataContainer& targetParameterContainer; + // Renaming to do const std::unordered_map& oldToNewPropertyNames; gd::String objectNameToUseForVariableAccessor; @@ -197,14 +190,14 @@ bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction, const gd::Expression& parameterValue, size_t parameterIndex, const gd::String& lastObjectName) { - if (!gd::EventsParameterReplacer::CanContainProperty( + if (!gd::EventsParameterReplacer::CanContainParameter( parameterMetadata.GetValueTypeMetadata())) { return; } auto node = parameterValue.GetRootNode(); if (node) { ExpressionParameterReplacer renamer( - platform, GetProjectScopedContainers(), targetParameterContainer, + platform, GetProjectScopedContainers(), parameterMetadata.GetValueTypeMetadata().IsVariableOnly(), oldToNewPropertyNames); node->Visit(renamer); @@ -221,14 +214,14 @@ bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction, bool EventsParameterReplacer::DoVisitEventExpression( gd::Expression& expression, const gd::ParameterMetadata& metadata) { - if (!gd::EventsParameterReplacer::CanContainProperty( + if (!gd::EventsParameterReplacer::CanContainParameter( metadata.GetValueTypeMetadata())) { return false; } auto node = expression.GetRootNode(); if (node) { ExpressionParameterReplacer renamer( - platform, GetProjectScopedContainers(), targetParameterContainer, + platform, GetProjectScopedContainers(), metadata.GetValueTypeMetadata().IsVariableOnly(), oldToNewPropertyNames); node->Visit(renamer); @@ -240,7 +233,7 @@ bool EventsParameterReplacer::DoVisitEventExpression( return false; } -bool EventsParameterReplacer::CanContainProperty( +bool EventsParameterReplacer::CanContainParameter( const gd::ValueTypeMetadata &valueTypeMetadata) { return valueTypeMetadata.IsVariable() || valueTypeMetadata.IsNumber() || valueTypeMetadata.IsString(); diff --git a/Core/GDCore/IDE/Events/EventsParameterReplacer.h b/Core/GDCore/IDE/Events/EventsParameterReplacer.h index effc5e7d97e5..c11bfaaf79f0 100644 --- a/Core/GDCore/IDE/Events/EventsParameterReplacer.h +++ b/Core/GDCore/IDE/Events/EventsParameterReplacer.h @@ -32,14 +32,12 @@ class GD_CORE_API EventsParameterReplacer public: EventsParameterReplacer( const gd::Platform &platform_, - const gd::ParameterMetadataContainer &targetParameterContainer_, const std::unordered_map &oldToNewPropertyNames_) : platform(platform_), - targetParameterContainer(targetParameterContainer_), oldToNewPropertyNames(oldToNewPropertyNames_){}; virtual ~EventsParameterReplacer(); - static bool CanContainProperty(const gd::ValueTypeMetadata &valueTypeMetadata); + static bool CanContainParameter(const gd::ValueTypeMetadata &valueTypeMetadata); private: bool DoVisitInstruction(gd::Instruction &instruction, @@ -48,7 +46,6 @@ class GD_CORE_API EventsParameterReplacer const gd::ParameterMetadata &metadata) override; const gd::Platform &platform; - const gd::ParameterMetadataContainer &targetParameterContainer; const std::unordered_map &oldToNewPropertyNames; }; diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.cpp b/Core/GDCore/IDE/Events/EventsRefactorer.cpp index 1b676888520e..bc44e1a0f245 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.cpp +++ b/Core/GDCore/IDE/Events/EventsRefactorer.cpp @@ -55,12 +55,11 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker { gd::ExpressionNode& node, const gd::String& objectName, const gd::String& objectNewName) { - if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, rootType, node)) { + // TODO Use the ProjectScopedContainers to check the targeted ObjectsContainer. ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, objectName, objectNewName); node.Visit(renamer); return renamer.HasDoneRenaming(); - } return false; } diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index 3a975c3b7d62..ee87ab1357ea 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -825,15 +825,18 @@ void WholeProjectRefactorer::RenameParameter( if (!parameters.HasParameterNamed(oldParameterName)) return; auto ¶meter = parameters.GetParameter(oldParameterName); - if (parameter.GetType() == "Object") { + if (parameter.GetValueTypeMetadata().IsObject()) { gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( project, projectScopedContainers, eventsFunction, oldParameterName, newParameterName, false); - } else if (parameter.GetType() == "Behavior") { + } else if (parameter.GetValueTypeMetadata().IsBehavior()) { size_t behaviorParameterIndex = parameters.GetParameterPosition(parameter); size_t objectParameterIndex = gd::ParameterMetadataTools::GetObjectParameterIndexFor( parameters, behaviorParameterIndex); + if (objectParameterIndex == gd::String::npos) { + return; + } const gd::String &objectName = parameters.GetParameter(objectParameterIndex).GetName(); gd::EventsBehaviorRenamer behaviorRenamer(project.GetCurrentPlatform(), @@ -845,7 +848,7 @@ void WholeProjectRefactorer::RenameParameter( std::unordered_map oldToNewParameterNames = { {oldParameterName, newParameterName}}; gd::EventsParameterReplacer eventsParameterReplacer( - project.GetCurrentPlatform(), parameters, oldToNewParameterNames); + project.GetCurrentPlatform(), oldToNewParameterNames); eventsParameterReplacer.Launch(eventsFunction.GetEvents(), projectScopedContainers); diff --git a/Core/tests/DummyPlatform.cpp b/Core/tests/DummyPlatform.cpp index 7986263e1fcf..fad74232d7b9 100644 --- a/Core/tests/DummyPlatform.cpp +++ b/Core/tests/DummyPlatform.cpp @@ -380,8 +380,8 @@ void SetupProjectWithDummyPlatform(gd::Project& project, "", "", "") - .AddParameter("object", _("Object 1 parameter")) - .AddParameter("object", _("Object 2 parameter")) + .AddParameter("object", "Object 1 parameter") + .AddParameter("object", "Object 2 parameter") .SetFunctionName("doSomethingWithObjects"); extension diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 57e37a1837ab..853a9b4d1eb7 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -91,6 +91,37 @@ CreateInstructionWithNumberParameter(gd::Project &project, return event.GetActions().Insert(instruction); } +const gd::Instruction & +CreateInstructionWithObjectParameter(gd::Project &project, + gd::EventsList &events, + const gd::String &objectName) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("MyExtension::DoSomethingWithObjects"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, objectName); + instruction.SetParameter(1, ""); + return event.GetActions().Insert(instruction); +} + +const gd::Instruction & +CreateInstructionWithBehaviorParameter(gd::Project &project, + gd::EventsList &events, + const gd::String &objectName, + const gd::String &behaviorName) { + gd::StandardEvent &event = dynamic_cast( + events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard")); + + gd::Instruction instruction; + instruction.SetType("MyExtension::BehaviorDoSomething"); + instruction.SetParametersCount(2); + instruction.SetParameter(0, objectName); + instruction.SetParameter(1, behaviorName); + return event.GetActions().Insert(instruction); +} + const gd::Instruction & CreateInstructionWithVariableParameter(gd::Project &project, gd::EventsList &events, @@ -2378,6 +2409,129 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { } } + SECTION("(Free) number parameter renamed (in expressions)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + auto &instruction = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), "MyParameter"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyParameter])"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, "MyParameter", + "MyRenamedParameter"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedParameter"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedParameter])"); + } + + SECTION("(Free) object parameter renamed (in expressions)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyObject") + .GetValueTypeMetadata() + .SetName("objectList") + .SetExtraInfo("MyExtension::Sprite"); + auto &instruction = CreateInstructionWithObjectParameter( + project, eventsFunction.GetEvents(), "MyObject"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), "MyObject.GetObjectStringWith1Param(0)"); + auto &instruction3 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyObject.GetObjectStringWith1Param(0)])"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, "MyObject", + "MyRenamedObject"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedObject"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyRenamedObject.GetObjectStringWith1Param(0)"); + REQUIRE(instruction3.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedObject.GetObjectStringWith1Param(0)])"); + } + + SECTION("(Free) behavior parameter renamed (in expressions)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyObject") + .GetValueTypeMetadata() + .SetName("objectList") + .SetExtraInfo("MyExtension::Sprite"); + eventsFunction.GetParameters() + .AddNewParameter("MyBehavior") + .GetValueTypeMetadata() + .SetName("behavior") + .SetExtraInfo("MyExtension::MyBehavior"); + auto &instruction = CreateInstructionWithBehaviorParameter( + project, eventsFunction.GetEvents(), "MyObject", "MyBehavior"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), "MyObject.MyBehavior::GetBehaviorStringWith1Param(0)"); + auto &instruction3 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyObject.MyBehavior::GetBehaviorStringWith1Param(0)])"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, "MyBehavior", + "MyRenamedBehavior"); + + REQUIRE(instruction.GetParameter(1).GetPlainString() == + "MyRenamedBehavior"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyObject.MyRenamedBehavior::GetBehaviorStringWith1Param(0)"); + REQUIRE(instruction3.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyObject.MyRenamedBehavior::GetBehaviorStringWith1Param(0)])"); + } + SECTION("(Free) events action parameter moved") { gd::Project project; gd::Platform platform; From d9ec7fd75ce0ee6c2142176032d049abbdad9ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 30 Nov 2024 23:58:53 +0100 Subject: [PATCH 15/26] Refactor object renaming to use ArbitraryEventsWorker. --- Core/GDCore/IDE/Events/EventsRefactorer.cpp | 238 +++++++------------- Core/GDCore/IDE/Events/EventsRefactorer.h | 38 ---- 2 files changed, 77 insertions(+), 199 deletions(-) diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.cpp b/Core/GDCore/IDE/Events/EventsRefactorer.cpp index bc44e1a0f245..37e21b734780 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.cpp +++ b/Core/GDCore/IDE/Events/EventsRefactorer.cpp @@ -22,6 +22,7 @@ #include "GDCore/Project/EventsBasedObject.h" #include "GDCore/Project/ProjectScopedContainers.h" #include "GDCore/IDE/Events/ExpressionTypeFinder.h" +#include "GDCore/IDE/Events/ArbitraryEventsWorker.h" using namespace std; @@ -294,183 +295,98 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker { const gd::String rootType; }; -bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::InstructionsList& actions, - gd::String oldName, - gd::String newName) { - bool somethingModified = false; - - for (std::size_t aId = 0; aId < actions.size(); ++aId) { - const gd::InstructionMetadata& instrInfos = - MetadataProvider::GetActionMetadata(platform, actions[aId].GetType()); - for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) { - // Replace object's name in parameters - if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) && - actions[aId].GetParameter(pNb).GetPlainString() == oldName) - actions[aId].SetParameter(pNb, gd::Expression(newName)); - // Replace object's name in expressions - else if (ParameterMetadata::IsExpression( - "number", instrInfos.parameters.GetParameter(pNb).GetType())) { - auto node = actions[aId].GetParameter(pNb).GetRootNode(); - - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) { - actions[aId].SetParameter( - pNb, ExpressionParser2NodePrinter::PrintNode(*node)); - } - } - // Replace object's name in text expressions - else if (ParameterMetadata::IsExpression( - "string", instrInfos.parameters.GetParameter(pNb).GetType())) { - auto node = actions[aId].GetParameter(pNb).GetRootNode(); - - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) { - actions[aId].SetParameter( - pNb, ExpressionParser2NodePrinter::PrintNode(*node)); - } - } - } +/** + * \brief Replace in expressions and in parameters of actions or conditions, + * references to the name of an object by another. + * + * \ingroup IDE + */ +class GD_CORE_API EventsObjectReplacer + : public ArbitraryEventsWorkerWithContext { +public: + EventsObjectReplacer(const gd::Platform &platform_, + const gd::String &oldObjectName_, + const gd::String &newObjectName_) + : platform(platform_), oldObjectName(oldObjectName_), + newObjectName(newObjectName_){}; + + virtual ~EventsObjectReplacer() {} + +private: + bool DoVisitInstruction(gd::Instruction &instruction, + bool isCondition) override { + const auto &metadata = isCondition + ? gd::MetadataProvider::GetConditionMetadata( + platform, instruction.GetType()) + : gd::MetadataProvider::GetActionMetadata( + platform, instruction.GetType()); + + gd::ParameterMetadataTools::IterateOverParametersWithIndex( + instruction.GetParameters(), metadata.GetParameters(), + [&](const gd::ParameterMetadata ¶meterMetadata, + const gd::Expression ¶meterValue, size_t parameterIndex, + const gd::String &lastObjectName) { + if (!gd::EventsObjectReplacer::CanContainObject( + parameterMetadata.GetValueTypeMetadata())) { + return; + } + auto node = parameterValue.GetRootNode(); + if (node) { + ExpressionObjectRenamer renamer( + platform, GetProjectScopedContainers(), + parameterMetadata.GetValueTypeMetadata().GetName(), + oldObjectName, newObjectName); + node->Visit(renamer); + + if (renamer.HasDoneRenaming()) { + instruction.SetParameter( + parameterIndex, + ExpressionParser2NodePrinter::PrintNode(*node)); + } + } + }); - if (!actions[aId].GetSubInstructions().empty()) - somethingModified = - RenameObjectInActions(platform, - projectScopedContainers, - actions[aId].GetSubInstructions(), - oldName, - newName) || - somethingModified; + return false; } - return somethingModified; -} - -bool EventsRefactorer::RenameObjectInConditions( - const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::InstructionsList& conditions, - gd::String oldName, - gd::String newName) { - bool somethingModified = false; - - for (std::size_t cId = 0; cId < conditions.size(); ++cId) { - const gd::InstructionMetadata& instrInfos = - MetadataProvider::GetConditionMetadata(platform, - conditions[cId].GetType()); - for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) { - // Replace object's name in parameters - if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) && - conditions[cId].GetParameter(pNb).GetPlainString() == oldName) - conditions[cId].SetParameter(pNb, gd::Expression(newName)); - // Replace object's name in expressions - else if (ParameterMetadata::IsExpression( - "number", instrInfos.parameters.GetParameter(pNb).GetType())) { - auto node = conditions[cId].GetParameter(pNb).GetRootNode(); - - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) { - conditions[cId].SetParameter( - pNb, ExpressionParser2NodePrinter::PrintNode(*node)); - } - } - // Replace object's name in text expressions - else if (ParameterMetadata::IsExpression( - "string", instrInfos.parameters.GetParameter(pNb).GetType())) { - auto node = conditions[cId].GetParameter(pNb).GetRootNode(); - - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) { - conditions[cId].SetParameter( - pNb, ExpressionParser2NodePrinter::PrintNode(*node)); - } - } + bool DoVisitEventExpression(gd::Expression &expression, + const gd::ParameterMetadata &metadata) override { + if (!gd::EventsObjectReplacer::CanContainObject( + metadata.GetValueTypeMetadata())) { + return false; } - - if (!conditions[cId].GetSubInstructions().empty()) - somethingModified = - RenameObjectInConditions(platform, - projectScopedContainers, - conditions[cId].GetSubInstructions(), - oldName, - newName) || - somethingModified; - } - - return somethingModified; -} - -bool EventsRefactorer::RenameObjectInEventParameters( - const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::Expression& expression, - gd::ParameterMetadata parameterMetadata, - gd::String oldName, - gd::String newName) { - bool somethingModified = false; - - if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType()) && - expression.GetPlainString() == oldName) - expression = gd::Expression(newName); - // Replace object's name in expressions - else if (ParameterMetadata::IsExpression("number", - parameterMetadata.GetType())) { auto node = expression.GetRootNode(); - - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) { - expression = ExpressionParser2NodePrinter::PrintNode(*node); + if (node) { + ExpressionObjectRenamer renamer(platform, GetProjectScopedContainers(), + metadata.GetValueTypeMetadata().GetName(), + oldObjectName, newObjectName); + node->Visit(renamer); + + if (renamer.HasDoneRenaming()) { + expression = ExpressionParser2NodePrinter::PrintNode(*node); + } } + + return false; } - // Replace object's name in text expressions - else if (ParameterMetadata::IsExpression("string", - parameterMetadata.GetType())) { - auto node = expression.GetRootNode(); - if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) { - expression = ExpressionParser2NodePrinter::PrintNode(*node); - } + bool CanContainObject(const gd::ValueTypeMetadata &valueTypeMetadata) { + return valueTypeMetadata.IsObject() || valueTypeMetadata.IsVariable() || + valueTypeMetadata.IsNumber() || valueTypeMetadata.IsString(); } - return somethingModified; -} + const gd::Platform &platform; + const gd::String &oldObjectName; + const gd::String &newObjectName; +}; void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform, const gd::ProjectScopedContainers& projectScopedContainers, gd::EventsList& events, gd::String oldName, gd::String newName) { - for (std::size_t i = 0; i < events.size(); ++i) { - vector conditionsVectors = - events[i].GetAllConditionsVectors(); - for (std::size_t j = 0; j < conditionsVectors.size(); ++j) { - bool somethingModified = RenameObjectInConditions( - platform, projectScopedContainers, *conditionsVectors[j], oldName, newName); - } - - vector actionsVectors = - events[i].GetAllActionsVectors(); - for (std::size_t j = 0; j < actionsVectors.size(); ++j) { - bool somethingModified = RenameObjectInActions( - platform, projectScopedContainers, *actionsVectors[j], oldName, newName); - } - - vector> - expressionsWithMetadata = events[i].GetAllExpressionsWithMetadata(); - for (std::size_t j = 0; j < expressionsWithMetadata.size(); ++j) { - gd::Expression* expression = expressionsWithMetadata[j].first; - gd::ParameterMetadata parameterMetadata = - expressionsWithMetadata[j].second; - bool somethingModified = RenameObjectInEventParameters(platform, - projectScopedContainers, - *expression, - parameterMetadata, - oldName, - newName); - } - - if (events[i].CanHaveSubEvents()) - RenameObjectInEvents(platform, - projectScopedContainers, - events[i].GetSubEvents(), - oldName, - newName); - } + gd::EventsObjectReplacer eventsParameterReplacer(platform, oldName, newName); + eventsParameterReplacer.Launch(events, projectScopedContainers); } bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform, diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.h b/Core/GDCore/IDE/Events/EventsRefactorer.h index 099c0a6457d4..ed4fdc2df330 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.h +++ b/Core/GDCore/IDE/Events/EventsRefactorer.h @@ -121,44 +121,6 @@ class GD_CORE_API EventsRefactorer { virtual ~EventsRefactorer(){}; private: - /** - * Replace all occurrences of an object name by another name in an action - * ( include : objects in parameters and in math/text expressions ). - * - * \return true if something was modified. - */ - static bool RenameObjectInActions(const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::InstructionsList& instructions, - gd::String oldName, - gd::String newName); - - /** - * Replace all occurrences of an object name by another name in a condition - * ( include : objects in parameters and in math/text expressions ). - * - * \return true if something was modified. - */ - static bool RenameObjectInConditions(const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::InstructionsList& instructions, - gd::String oldName, - gd::String newName); - /** - * Replace all occurrences of an object name by another name in an expression - * with the specified metadata - * ( include : objects or objects in math/text expressions ). - * - * \return true if something was modified. - */ - static bool RenameObjectInEventParameters( - const gd::Platform& platform, - const gd::ProjectScopedContainers& projectScopedContainers, - gd::Expression& expression, - gd::ParameterMetadata parameterMetadata, - gd::String oldName, - gd::String newName); - /** * Remove all conditions of the list using an object * From ed6968df5fa8379a218b6dcc5eedd8732d69dbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 1 Dec 2024 00:54:21 +0100 Subject: [PATCH 16/26] Check renamed object container. --- Core/GDCore/IDE/Events/EventsRefactorer.cpp | 39 ++++++++++++------- Core/GDCore/IDE/Events/EventsRefactorer.h | 1 + Core/GDCore/IDE/WholeProjectRefactorer.cpp | 35 +++++++++++------ Core/GDCore/IDE/WholeProjectRefactorer.h | 8 ++++ Core/tests/WholeProjectRefactorer.cpp | 17 ++++---- GDevelop.js/Bindings/Bindings.idl | 9 ++++- GDevelop.js/types.d.ts | 4 +- GDevelop.js/types/gdeventsrefactorer.js | 2 +- GDevelop.js/types/gdwholeprojectrefactorer.js | 2 +- .../index.js | 3 ++ 10 files changed, 82 insertions(+), 38 deletions(-) diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.cpp b/Core/GDCore/IDE/Events/EventsRefactorer.cpp index 37e21b734780..fc01bcc5a77e 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.cpp +++ b/Core/GDCore/IDE/Events/EventsRefactorer.cpp @@ -52,17 +52,14 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker { static bool Rename(const gd::Platform &platform, const gd::ProjectScopedContainers &projectScopedContainers, - const gd::String &rootType, - gd::ExpressionNode& node, - const gd::String& objectName, - const gd::String& objectNewName) { - // TODO Use the ProjectScopedContainers to check the targeted ObjectsContainer. - ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, objectName, objectNewName); - node.Visit(renamer); + const gd::String &rootType, gd::ExpressionNode &node, + const gd::String &objectName, + const gd::String &objectNewName) { + ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, + objectName, objectNewName); + node.Visit(renamer); - return renamer.HasDoneRenaming(); - - return false; + return renamer.HasDoneRenaming(); } bool HasDoneRenaming() const { return hasDoneRenaming; } @@ -305,16 +302,24 @@ class GD_CORE_API EventsObjectReplacer : public ArbitraryEventsWorkerWithContext { public: EventsObjectReplacer(const gd::Platform &platform_, + const gd::ObjectsContainer &targetedObjectsContainer_, const gd::String &oldObjectName_, const gd::String &newObjectName_) - : platform(platform_), oldObjectName(oldObjectName_), - newObjectName(newObjectName_){}; + : platform(platform_), + targetedObjectsContainer(targetedObjectsContainer_), + oldObjectName(oldObjectName_), newObjectName(newObjectName_){}; virtual ~EventsObjectReplacer() {} private: bool DoVisitInstruction(gd::Instruction &instruction, bool isCondition) override { + if (&targetedObjectsContainer != + GetProjectScopedContainers() + .GetObjectsContainersList() + .GetObjectsContainerFromObjectName(oldObjectName)) { + return false; + } const auto &metadata = isCondition ? gd::MetadataProvider::GetConditionMetadata( platform, instruction.GetType()) @@ -351,6 +356,12 @@ class GD_CORE_API EventsObjectReplacer bool DoVisitEventExpression(gd::Expression &expression, const gd::ParameterMetadata &metadata) override { + if (&targetedObjectsContainer != + GetProjectScopedContainers() + .GetObjectsContainersList() + .GetObjectsContainerFromObjectName(oldObjectName)) { + return false; + } if (!gd::EventsObjectReplacer::CanContainObject( metadata.GetValueTypeMetadata())) { return false; @@ -376,6 +387,7 @@ class GD_CORE_API EventsObjectReplacer } const gd::Platform &platform; + const gd::ObjectsContainer &targetedObjectsContainer; const gd::String &oldObjectName; const gd::String &newObjectName; }; @@ -383,9 +395,10 @@ class GD_CORE_API EventsObjectReplacer void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform, const gd::ProjectScopedContainers& projectScopedContainers, gd::EventsList& events, + const gd::ObjectsContainer &targetedObjectsContainer, gd::String oldName, gd::String newName) { - gd::EventsObjectReplacer eventsParameterReplacer(platform, oldName, newName); + gd::EventsObjectReplacer eventsParameterReplacer(platform, targetedObjectsContainer, oldName, newName); eventsParameterReplacer.Launch(events, projectScopedContainers); } diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.h b/Core/GDCore/IDE/Events/EventsRefactorer.h index ed4fdc2df330..73a49504c7a6 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.h +++ b/Core/GDCore/IDE/Events/EventsRefactorer.h @@ -83,6 +83,7 @@ class GD_CORE_API EventsRefactorer { static void RenameObjectInEvents(const gd::Platform& platform, const gd::ProjectScopedContainers& projectScopedContainers, gd::EventsList& events, + const gd::ObjectsContainer &targetedObjectsContainer, gd::String oldName, gd::String newName); diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index ee87ab1357ea..0ebb1c8370ba 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -819,16 +819,17 @@ void WholeProjectRefactorer::RenameObjectEventsFunction( void WholeProjectRefactorer::RenameParameter( gd::Project &project, gd::ProjectScopedContainers &projectScopedContainers, - gd::EventsFunction &eventsFunction, const gd::String &oldParameterName, - const gd::String &newParameterName) { + gd::EventsFunction &eventsFunction, + const gd::ObjectsContainer ¶meterObjectsContainer, + const gd::String &oldParameterName, const gd::String &newParameterName) { auto ¶meters = eventsFunction.GetParameters(); if (!parameters.HasParameterNamed(oldParameterName)) return; auto ¶meter = parameters.GetParameter(oldParameterName); if (parameter.GetValueTypeMetadata().IsObject()) { gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( - project, projectScopedContainers, eventsFunction, oldParameterName, - newParameterName, false); + project, projectScopedContainers, eventsFunction, + parameterObjectsContainer, oldParameterName, newParameterName, false); } else if (parameter.GetValueTypeMetadata().IsBehavior()) { size_t behaviorParameterIndex = parameters.GetParameterPosition(parameter); size_t objectParameterIndex = @@ -1786,6 +1787,15 @@ void WholeProjectRefactorer::BehaviorsAddedToObjectInScene( void WholeProjectRefactorer::ObjectOrGroupRenamedInScene( gd::Project &project, gd::Layout &layout, const gd::String &oldName, const gd::String &newName, bool isObjectGroup) { + gd::WholeProjectRefactorer::ObjectOrGroupRenamedInScene( + project, layout, layout.GetObjects(), oldName, newName, isObjectGroup); +} + +void WholeProjectRefactorer::ObjectOrGroupRenamedInScene( + gd::Project &project, gd::Layout &layout, + const gd::ObjectsContainer &targetedObjectsContainer, + const gd::String &oldName, const gd::String &newName, bool isObjectGroup) { + if (oldName == newName || newName.empty() || oldName.empty()) return; @@ -1795,7 +1805,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInScene( // Rename object in the current layout gd::EventsRefactorer::RenameObjectInEvents( project.GetCurrentPlatform(), projectScopedContainers, layout.GetEvents(), - oldName, newName); + layout.GetObjects(), oldName, newName); // Object groups can't have instances or be in other groups if (!isObjectGroup) { @@ -1812,7 +1822,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInScene( auto &externalEvents = project.GetExternalEvents(externalEventsName); gd::EventsRefactorer::RenameObjectInEvents( project.GetCurrentPlatform(), projectScopedContainers, - externalEvents.GetEvents(), oldName, newName); + externalEvents.GetEvents(), layout.GetObjects(), oldName, newName); } // Rename object in external layouts @@ -2058,8 +2068,8 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject( eventsBasedObject.GetEventsFunctions().GetInternalVector()) { auto *function = functionUniquePtr.get(); WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( - project, projectScopedContainers, *function, oldName, newName, - isObjectGroup); + project, projectScopedContainers, *function, + eventsBasedObject.GetObjects(), oldName, newName, isObjectGroup); } // Object groups can't have instances or be in other groups @@ -2076,11 +2086,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject( void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( gd::Project &project, const gd::ProjectScopedContainers &projectScopedContainers, - gd::EventsFunction &eventsFunction, const gd::String &oldName, - const gd::String &newName, bool isObjectGroup) { + gd::EventsFunction &eventsFunction, + const gd::ObjectsContainer &targetedObjectsContainer, + const gd::String &oldName, const gd::String &newName, bool isObjectGroup) { gd::EventsRefactorer::RenameObjectInEvents( project.GetCurrentPlatform(), projectScopedContainers, - eventsFunction.GetEvents(), oldName, newName); + eventsFunction.GetEvents(), targetedObjectsContainer, oldName, newName); // Object groups can't be in other groups if (!isObjectGroup) { @@ -2106,7 +2117,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRenamed( if (layout.GetObjects().HasObjectNamed(oldName)) continue; - ObjectOrGroupRenamedInScene(project, layout, oldName, newName, + ObjectOrGroupRenamedInScene(project, layout, project.GetObjects(), oldName, newName, isObjectGroup); } } diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index 8a0d0c573648..63749f7844c2 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -180,6 +180,7 @@ class GD_CORE_API WholeProjectRefactorer { RenameParameter(gd::Project &project, gd::ProjectScopedContainers &projectScopedContainers, gd::EventsFunction &eventsFunction, + const gd::ObjectsContainer ¶meterObjectsContainer, const gd::String &oldParameterName, const gd::String &newParameterName); @@ -556,6 +557,7 @@ class GD_CORE_API WholeProjectRefactorer { gd::Project& project, const gd::ProjectScopedContainers &projectScopedContainers, gd::EventsFunction& eventsFunction, + const gd::ObjectsContainer &targetedObjectsContainer, const gd::String& oldName, const gd::String& newName, bool isObjectGroup); @@ -676,6 +678,12 @@ class GD_CORE_API WholeProjectRefactorer { virtual ~WholeProjectRefactorer(){}; private: + static void ObjectOrGroupRenamedInScene(gd::Project &project, + gd::Layout &scene, + const gd::ObjectsContainer &targetedObjectsContainer, + const gd::String &oldName, + const gd::String &newName, + bool isObjectGroup); static std::vector GetAssociatedExternalLayouts( gd::Project& project, gd::Layout& layout); static std::vector diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 853a9b4d1eb7..86a10a1ca559 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -1672,7 +1672,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // Trigger the refactoring after the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( project, projectScopedContainers, eventsFunction, - "Object1", "RenamedObject1", + parametersObjectsContainer, "Object1", "RenamedObject1", /* isObjectGroup=*/false); REQUIRE(objectGroup.Find("Object1") == false); @@ -1709,7 +1709,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { // Trigger the refactoring after the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( project, projectScopedContainers, eventsFunction, - "ObjectWithMyBehavior", "RenamedObjectWithMyBehavior", + parametersObjectsContainer, "ObjectWithMyBehavior", + "RenamedObjectWithMyBehavior", /* isObjectGroup=*/false); // Check object name has been renamed in action parameters. @@ -2436,8 +2437,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { project, eventsExtension, eventsFunction, parametersObjectsContainer, parameterVariablesContainer); gd::WholeProjectRefactorer::RenameParameter( - project, projectScopedContainers, eventsFunction, "MyParameter", - "MyRenamedParameter"); + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter", "MyRenamedParameter"); REQUIRE(instruction.GetParameter(0).GetPlainString() == "MyRenamedParameter"); @@ -2475,8 +2476,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { project, eventsExtension, eventsFunction, parametersObjectsContainer, parameterVariablesContainer); gd::WholeProjectRefactorer::RenameParameter( - project, projectScopedContainers, eventsFunction, "MyObject", - "MyRenamedObject"); + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyObject", "MyRenamedObject"); REQUIRE(instruction.GetParameter(0).GetPlainString() == "MyRenamedObject"); @@ -2521,8 +2522,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { project, eventsExtension, eventsFunction, parametersObjectsContainer, parameterVariablesContainer); gd::WholeProjectRefactorer::RenameParameter( - project, projectScopedContainers, eventsFunction, "MyBehavior", - "MyRenamedBehavior"); + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyBehavior", "MyRenamedBehavior"); REQUIRE(instruction.GetParameter(1).GetPlainString() == "MyRenamedBehavior"); diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 63a80492d44b..bf01bf2f10ea 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2443,7 +2443,13 @@ interface VectorEventsSearchResult { }; interface EventsRefactorer { - void STATIC_RenameObjectInEvents([Const, Ref] Platform platform, [Ref] ProjectScopedContainers projectScopedContainers, [Ref] EventsList events, [Const] DOMString oldName, [Const] DOMString newName); + void STATIC_RenameObjectInEvents( + [Const, Ref] Platform platform, + [Ref] ProjectScopedContainers projectScopedContainers, + [Ref] EventsList events, + [Const, Ref] ObjectsContainer targetedObjectsContainer, + [Const] DOMString oldName, + [Const] DOMString newName); [Value] VectorEventsSearchResult STATIC_ReplaceStringInEvents([Ref] ObjectsContainer project, [Ref] ObjectsContainer layout, [Ref] EventsList events, [Const] DOMString toReplace, [Const] DOMString newString, boolean matchCase, boolean inConditions, boolean inActions, boolean inEventStrings); [Value] VectorEventsSearchResult STATIC_SearchInEvents([Const, Ref] Platform platform, [Ref] EventsList events, [Const] DOMString search, boolean matchCase, boolean inConditions, boolean inActions, boolean inEventStrings, boolean inEventSentences); }; @@ -2684,6 +2690,7 @@ interface WholeProjectRefactorer { [Ref] Project project, [Ref] ProjectScopedContainers projectScopedContainers, [Ref] EventsFunction eventsFunction, + [Const, Ref] ObjectsContainer parameterObjectsContainer, [Const] DOMString oldName, [Const] DOMString newName, boolean isObjectGroup); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 15175d301010..ad96f61e9393 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1864,7 +1864,7 @@ export class VectorEventsSearchResult extends EmscriptenObject { } export class EventsRefactorer extends EmscriptenObject { - static renameObjectInEvents(platform: Platform, projectScopedContainers: ProjectScopedContainers, events: EventsList, oldName: string, newName: string): void; + static renameObjectInEvents(platform: Platform, projectScopedContainers: ProjectScopedContainers, events: EventsList, targetedObjectsContainer: ObjectsContainer, oldName: string, newName: string): void; static replaceStringInEvents(project: ObjectsContainer, layout: ObjectsContainer, events: EventsList, toReplace: string, newString: string, matchCase: boolean, inConditions: boolean, inActions: boolean, inEventStrings: boolean): VectorEventsSearchResult; static searchInEvents(platform: Platform, events: EventsList, search: string, matchCase: boolean, inConditions: boolean, inActions: boolean, inEventStrings: boolean, inEventSentences: boolean): VectorEventsSearchResult; } @@ -1934,7 +1934,7 @@ export class WholeProjectRefactorer extends EmscriptenObject { static objectOrGroupRenamedInScene(project: Project, scene: Layout, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInScene(project: Project, scene: Layout, objectName: string): void; static behaviorsAddedToObjectInScene(project: Project, scene: Layout, objectName: string): void; - static objectOrGroupRenamedInEventsFunction(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, oldName: string, newName: string, isObjectGroup: boolean): void; + static objectOrGroupRenamedInEventsFunction(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsFunction(project: Project, eventsFunction: EventsFunction, objectName: string): void; static objectOrGroupRenamedInEventsBasedObject(project: Project, projectScopedContainers: ProjectScopedContainers, eventsBasedObject: EventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsBasedObject(project: Project, eventsBasedObject: EventsBasedObject, objectName: string): void; diff --git a/GDevelop.js/types/gdeventsrefactorer.js b/GDevelop.js/types/gdeventsrefactorer.js index f187aa8e09ff..3c17c6045f46 100644 --- a/GDevelop.js/types/gdeventsrefactorer.js +++ b/GDevelop.js/types/gdeventsrefactorer.js @@ -1,6 +1,6 @@ // Automatically generated by GDevelop.js/scripts/generate-types.js declare class gdEventsRefactorer { - static renameObjectInEvents(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, events: gdEventsList, oldName: string, newName: string): void; + static renameObjectInEvents(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, events: gdEventsList, targetedObjectsContainer: gdObjectsContainer, oldName: string, newName: string): void; static replaceStringInEvents(project: gdObjectsContainer, layout: gdObjectsContainer, events: gdEventsList, toReplace: string, newString: string, matchCase: boolean, inConditions: boolean, inActions: boolean, inEventStrings: boolean): gdVectorEventsSearchResult; static searchInEvents(platform: gdPlatform, events: gdEventsList, search: string, matchCase: boolean, inConditions: boolean, inActions: boolean, inEventStrings: boolean, inEventSentences: boolean): gdVectorEventsSearchResult; delete(): void; diff --git a/GDevelop.js/types/gdwholeprojectrefactorer.js b/GDevelop.js/types/gdwholeprojectrefactorer.js index 346f2b793bd4..a1833fb3c99c 100644 --- a/GDevelop.js/types/gdwholeprojectrefactorer.js +++ b/GDevelop.js/types/gdwholeprojectrefactorer.js @@ -37,7 +37,7 @@ declare class gdWholeProjectRefactorer { static objectOrGroupRenamedInScene(project: gdProject, scene: gdLayout, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInScene(project: gdProject, scene: gdLayout, objectName: string): void; static behaviorsAddedToObjectInScene(project: gdProject, scene: gdLayout, objectName: string): void; - static objectOrGroupRenamedInEventsFunction(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, oldName: string, newName: string, isObjectGroup: boolean): void; + static objectOrGroupRenamedInEventsFunction(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsFunction(project: gdProject, eventsFunction: gdEventsFunction, objectName: string): void; static objectOrGroupRenamedInEventsBasedObject(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string, isObjectGroup: boolean): void; static objectRemovedInEventsBasedObject(project: gdProject, eventsBasedObject: gdEventsBasedObject, objectName: string): void; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js index 700bc25fb55b..192c714ce41f 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js @@ -102,6 +102,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< project, projectScopedContainersAccessor, eventsFunction, + objectsContainer } = this.props; // newName is supposed to have been already validated @@ -112,6 +113,8 @@ export default class EventsFunctionConfigurationEditor extends React.Component< project, projectScopedContainersAccessor.get(), eventsFunction, + // This is the ObjectsContainer generated from parameters + objectsContainer, group.getName(), newName, /* isObjectGroup=*/ true From a564adb6d31b612f3250a3f8490bdaad4068823b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 1 Dec 2024 12:34:32 +0100 Subject: [PATCH 17/26] Fix tests comments. --- Core/tests/WholeProjectRefactorer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 86a10a1ca559..94d5bd1a95df 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -1560,7 +1560,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { auto &layout = project.GetLayout("Scene"); - // Trigger the refactoring after the renaming of an object + // Trigger the refactoring before the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInScene( project, layout, "ObjectWithMyBehavior", "RenamedObjectWithMyBehavior", @@ -1588,7 +1588,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { auto &layout = project.GetLayout("Scene"); - // Trigger the refactoring after the renaming of a group + // Trigger the refactoring before the renaming of a group gd::WholeProjectRefactorer::ObjectOrGroupRenamedInScene( project, layout, "GroupWithMyBehavior", "RenamedGroupWithMyBehavior", /* isObjectGroup=*/true); @@ -1669,7 +1669,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { project, eventsExtension, eventsFunction, parametersObjectsContainer, parameterVariablesContainer); - // Trigger the refactoring after the renaming of an object + // Trigger the refactoring before the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( project, projectScopedContainers, eventsFunction, parametersObjectsContainer, "Object1", "RenamedObject1", @@ -1706,7 +1706,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { objectWithMyBehavior.GetVariables().InsertNew("MyVariable"); objectWithMyBehavior.GetVariables().InsertNew("MyStructureVariable").CastTo(gd::Variable::Structure); - // Trigger the refactoring after the renaming of an object + // Trigger the refactoring before the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction( project, projectScopedContainers, eventsFunction, parametersObjectsContainer, "ObjectWithMyBehavior", @@ -1826,7 +1826,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { project, eventsExtension, eventsBasedObject, parametersObjectsContainer); - // Trigger the refactoring after the renaming of an object + // Trigger the refactoring before the renaming of an object gd::WholeProjectRefactorer::ObjectOrGroupRenamedInEventsBasedObject( project, projectScopedContainers, eventsBasedObject, "ObjectWithMyBehavior", "RenamedObjectWithMyBehavior", From 48087a463094c23c09a51a1113efec2ccc08bcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 1 Dec 2024 13:02:48 +0100 Subject: [PATCH 18/26] Fix unintended renaming of object where it's a variable. --- .../Extensions/Metadata/ValueTypeMetadata.h | 12 +- Core/GDCore/IDE/Events/EventsRefactorer.cpp | 4 +- Core/tests/WholeProjectRefactorer.cpp | 121 ++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h index 3dde5c03a7cc..7d325922585c 100644 --- a/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h +++ b/Core/GDCore/Extensions/Metadata/ValueTypeMetadata.h @@ -135,7 +135,17 @@ class GD_CORE_API ValueTypeMetadata { * and ExpressionAutocompletion) and in the EventsCodeGenerator. */ bool IsVariable() const { - return gd::ValueTypeMetadata::GetPrimitiveValueType(name) == "variable"; + return gd::ValueTypeMetadata::IsVariable(name); + } + + /** + * \brief Return true if the type of the parameter is a variable. + * \note If you had a new type of parameter, also add it in the IDE ( + * see EventsFunctionParametersEditor, ParameterRenderingService + * and ExpressionAutocompletion) and in the EventsCodeGenerator. + */ + static bool IsVariable(const gd::String &type) { + return gd::ValueTypeMetadata::GetPrimitiveValueType(type) == "variable"; } /** diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.cpp b/Core/GDCore/IDE/Events/EventsRefactorer.cpp index fc01bcc5a77e..33a9a2ab5f65 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.cpp +++ b/Core/GDCore/IDE/Events/EventsRefactorer.cpp @@ -80,7 +80,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker { void OnVisitVariableNode(VariableNode& node) override { auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node); - if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) { + if (gd::ValueTypeMetadata::IsVariable(type)) { // Nothing to do (this can't reference an object) } else { if (node.name == objectName) { @@ -116,7 +116,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker { node.identifierName == objectName) { hasDoneRenaming = true; node.identifierName = objectNewName; - } else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) { + } else if (gd::ValueTypeMetadata::IsVariable(type)) { // Nothing to do (this can't reference an object) } else { if (node.identifierName == objectName) { diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index 94d5bd1a95df..e5d1b2d143f6 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -2446,6 +2446,44 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedParameter])"); } + SECTION("(Free) number parameter not renamed (in variable parameter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + // Parameters can't actually be used in "variable" parameters. + auto &instruction = CreateInstructionWithVariableParameter( + project, eventsFunction.GetEvents(), "MyParameter"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyParameter)"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter", "MyRenamedParameter"); + + // "variable" parameters are left untouched. + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyParameter"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyParameter)"); + } + SECTION("(Free) object parameter renamed (in expressions)") { gd::Project project; gd::Platform platform; @@ -2487,6 +2525,45 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedObject.GetObjectStringWith1Param(0)])"); } + SECTION("(Free) object parameter not renamed (in variable parameter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyObject") + .GetValueTypeMetadata() + .SetName("objectList") + .SetExtraInfo("MyExtension::Sprite"); + // Parameters can't actually be used in "variable" parameters. + auto &instruction = CreateInstructionWithVariableParameter( + project, eventsFunction.GetEvents(), "MyObject"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyObject)"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyObject", "MyRenamedObject"); + + // "variable" parameters are left untouched. + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyObject"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyObject)"); + } + SECTION("(Free) behavior parameter renamed (in expressions)") { gd::Project project; gd::Platform platform; @@ -2533,6 +2610,50 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyObject.MyRenamedBehavior::GetBehaviorStringWith1Param(0)])"); } + SECTION("(Free) behavior parameter not renamed (in variable parameter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyObject") + .GetValueTypeMetadata() + .SetName("objectList") + .SetExtraInfo("MyExtension::Sprite"); + eventsFunction.GetParameters() + .AddNewParameter("MyBehavior") + .GetValueTypeMetadata() + .SetName("behavior") + .SetExtraInfo("MyExtension::MyBehavior"); + // Parameters can't actually be used in "variable" parameters. + auto &instruction = CreateInstructionWithVariableParameter( + project, eventsFunction.GetEvents(), "MyBehavior"); + auto &instruction2 = CreateInstructionWithNumberParameter( + project, eventsFunction.GetEvents(), + "MyExtension::GetVariableAsNumber(MyBehavior)"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyBehavior", "MyRenamedBehavior"); + + // "variable" parameters are left untouched. + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyBehavior"); + REQUIRE(instruction2.GetParameter(0).GetPlainString() == + "MyExtension::GetVariableAsNumber(MyBehavior)"); + } + SECTION("(Free) events action parameter moved") { gd::Project project; gd::Platform platform; From fb298fab2b3f65faaf5fe7f8e5b7ce7a50634693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 1 Dec 2024 15:40:18 +0100 Subject: [PATCH 19/26] Plug the refactor in the editor. --- GDevelop.js/Bindings/Bindings.idl | 7 ++ GDevelop.js/Bindings/Wrapper.cpp | 1 + GDevelop.js/types.d.ts | 1 + GDevelop.js/types/gdwholeprojectrefactorer.js | 1 + .../EventsFunctionParametersEditor.js | 72 ++++++++++++++----- .../index.js | 9 ++- .../EventsFunctionsExtensionEditor/index.js | 23 ++++++ .../EventsFunctionExtractorDialog.js | 3 + ...entsFunctionConfigurationEditor.stories.js | 12 ++++ 9 files changed, 109 insertions(+), 20 deletions(-) diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index bf01bf2f10ea..1ca7099c16e7 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2529,6 +2529,13 @@ interface WholeProjectRefactorer { [Const, Ref] EventsBasedObject eventsBasedObject, [Const] DOMString oldName, [Const] DOMString newName); + void STATIC_RenameParameter( + [Ref] Project project, + [Ref] ProjectScopedContainers projectScopedContainers, + [Ref] EventsFunction eventsFunction, + [Const, Ref] ObjectsContainer parameterObjectsContainer, + [Const] DOMString oldName, + [Const] DOMString newName); void STATIC_MoveEventsFunctionParameter( [Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index 4a06570ce9c6..a7ba3b1bb678 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -727,6 +727,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_RenameEventsFunction RenameEventsFunction #define STATIC_RenameBehaviorEventsFunction RenameBehaviorEventsFunction #define STATIC_RenameObjectEventsFunction RenameObjectEventsFunction +#define STATIC_RenameParameter RenameParameter #define STATIC_MoveEventsFunctionParameter MoveEventsFunctionParameter #define STATIC_MoveBehaviorEventsFunctionParameter \ MoveBehaviorEventsFunctionParameter diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index ad96f61e9393..8e2c31a0c43b 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1906,6 +1906,7 @@ export class WholeProjectRefactorer extends EmscriptenObject { static renameEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void; static renameBehaviorEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, oldName: string, newName: string): void; static renameObjectEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, oldName: string, newName: string): void; + static renameParameter(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, oldName: string, newName: string): void; static moveEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, functionName: string, oldIndex: number, newIndex: number): void; static moveBehaviorEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, functionName: string, oldIndex: number, newIndex: number): void; static moveObjectEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; diff --git a/GDevelop.js/types/gdwholeprojectrefactorer.js b/GDevelop.js/types/gdwholeprojectrefactorer.js index a1833fb3c99c..8162b5b55926 100644 --- a/GDevelop.js/types/gdwholeprojectrefactorer.js +++ b/GDevelop.js/types/gdwholeprojectrefactorer.js @@ -9,6 +9,7 @@ declare class gdWholeProjectRefactorer { static renameEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void; static renameBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void; static renameObjectEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string): void; + static renameParameter(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, oldName: string, newName: string): void; static moveEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, functionName: string, oldIndex: number, newIndex: number): void; static moveBehaviorEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, functionName: string, oldIndex: number, newIndex: number): void; static moveObjectEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js index eaa864a874d0..d7109b32e261 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js @@ -124,6 +124,11 @@ type Props = {| newIndex: number, done: (boolean) => void ) => void, + onFunctionParameterWillBeRenamed: ( + eventsFunction: gdEventsFunction, + oldName: string, + newName: string + ) => void, |}; export const EventsFunctionParametersEditor = ({ @@ -140,6 +145,7 @@ export const EventsFunctionParametersEditor = ({ onMoveFreeEventsParameter, onMoveBehaviorEventsParameter, onMoveObjectEventsParameter, + onFunctionParameterWillBeRenamed, }: Props) => { const scrollView = React.useRef(null); const [ @@ -193,18 +199,57 @@ export const EventsFunctionParametersEditor = ({ [eventsFunction, firstParameterIndex, freezeParameters] ); + const renameParameter = React.useCallback( + (parameter: gdParameterMetadata, newName: string) => { + if (newName === parameter.getName()) { + return; + } + const projectScopedContainers = projectScopedContainersAccessor.get(); + const validatedNewName = getValidatedParameterName( + eventsFunction.getParameters(), + projectScopedContainers, + newName + ); + onFunctionParameterWillBeRenamed( + eventsFunction, + parameter.getName(), + validatedNewName + ); + parameter.setName(validatedNewName); + forceUpdate(); + onParametersUpdated(); + }, + [ + eventsFunction, + forceUpdate, + onFunctionParameterWillBeRenamed, + onParametersUpdated, + projectScopedContainersAccessor, + ] + ); + const addParameterAt = React.useCallback( (index: number) => { const parameters = eventsFunction.getParameters(); - const newName = newNameGenerator('Parameter', name => - parameters.hasParameterNamed(name) + const projectScopedContainers = projectScopedContainersAccessor.get(); + const validatedNewName = getValidatedParameterName( + eventsFunction.getParameters(), + projectScopedContainers, + 'Parameter' ); - parameters.insertNewParameter(newName, index).setType('objectList'); + parameters + .insertNewParameter(validatedNewName, index) + .setType('objectList'); forceUpdate(); onParametersUpdated(); - setJustAddedParameterName(newName); + setJustAddedParameterName(validatedNewName); }, - [eventsFunction, forceUpdate, onParametersUpdated] + [ + eventsFunction, + forceUpdate, + onParametersUpdated, + projectScopedContainersAccessor, + ] ); const addParameter = React.useCallback( @@ -643,20 +688,9 @@ export const EventsFunctionParametersEditor = ({ margin="none" translatableHintText={t`Enter the parameter name (mandatory)`} value={parameter.getName()} - onChange={newName => { - if (newName === parameter.getName()) { - return; - } - const projectScopedContainers = projectScopedContainersAccessor.get(); - const validatedNewName = getValidatedParameterName( - parameters, - projectScopedContainers, - newName - ); - parameter.setName(validatedNewName); - forceUpdate(); - onParametersUpdated(); - }} + onChange={newName => + renameParameter(parameter, newName) + } disabled={isParameterDisabled(i)} fullWidth /> diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js index 192c714ce41f..b65fe5a226a9 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js @@ -52,6 +52,11 @@ type Props = {| newIndex: number, done: (boolean) => void ) => void, + onFunctionParameterWillBeRenamed: ( + eventsFunction: gdEventsFunction, + oldName: string, + newName: string + ) => void, unsavedChanges?: ?UnsavedChanges, getFunctionGroupNames?: () => string[], |}; @@ -102,7 +107,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< project, projectScopedContainersAccessor, eventsFunction, - objectsContainer + objectsContainer, } = this.props; // newName is supposed to have been already validated @@ -150,6 +155,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< getFunctionGroupNames, eventsFunctionsContainer, eventsFunctionsExtension, + onFunctionParameterWillBeRenamed, } = this.props; const hasLegacyFunctionObjectGroups = @@ -215,6 +221,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< onMoveFreeEventsParameter={onMoveFreeEventsParameter} onMoveBehaviorEventsParameter={onMoveBehaviorEventsParameter} onMoveObjectEventsParameter={onMoveObjectEventsParameter} + onFunctionParameterWillBeRenamed={onFunctionParameterWillBeRenamed} key={eventsFunction ? eventsFunction.ptr : null} /> ) : null} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js index 77769928d87c..afc8658bc84e 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js @@ -1058,6 +1058,26 @@ export default class EventsFunctionsExtensionEditor extends React.Component< ); }; + _onFunctionParameterWillBeRenamed = ( + eventsFunction: gdEventsFunction, + oldName: string, + newName: string + ) => { + if (!this._projectScopedContainersAccessor) { + return; + } + const projectScopedContainers = this._projectScopedContainersAccessor.get(); + const { project } = this.props; + gd.WholeProjectRefactorer.renameParameter( + project, + projectScopedContainers, + eventsFunction, + this._objectsContainer, + oldName, + newName + ); + }; + _editOptions = (open: boolean = true) => { this.setState({ editOptionsDialogOpen: open, @@ -1347,6 +1367,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component< onMoveObjectEventsParameter={this._makeMoveObjectEventsParameter( i18n )} + onFunctionParameterWillBeRenamed={ + this._onFunctionParameterWillBeRenamed + } unsavedChanges={this.props.unsavedChanges} getFunctionGroupNames={this._getFunctionGroupNames} /> diff --git a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js index 228520192363..1a828f5e48db 100644 --- a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js +++ b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js @@ -326,6 +326,9 @@ export default class EventsFunctionExtractorDialog extends React.Component< // Force the dialog to adapt its size this.forceUpdate(); }} + onFunctionParameterWillBeRenamed={() => { + // Won't happen as the editor is freezed. + }} freezeParameters /> )} diff --git a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js index f78fbb358754..e8b8a4f72bed 100644 --- a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js @@ -32,6 +32,9 @@ export const DefaultFreeFunction = () => ( eventsFunctionsContainer={testProject.testEventsFunctionsExtension} eventsFunctionsExtension={testProject.testEventsFunctionsExtension} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} + onFunctionParameterWillBeRenamed={action( + 'onFunctionParameterWillBeRenamed' + )} /> ); @@ -52,6 +55,9 @@ export const DefaultBehaviorFunction = () => ( eventsFunctionsContainer={testProject.testEventsBasedBehavior.getEventsFunctions()} eventsFunctionsExtension={testProject.testEventsFunctionsExtension} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} + onFunctionParameterWillBeRenamed={action( + 'onFunctionParameterWillBeRenamed' + )} /> ); @@ -72,6 +78,9 @@ export const DefaultBehaviorLifecycleFunction = () => ( eventsFunctionsContainer={testProject.testEventsBasedBehavior.getEventsFunctions()} eventsFunctionsExtension={testProject.testEventsFunctionsExtension} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} + onFunctionParameterWillBeRenamed={action( + 'onFunctionParameterWillBeRenamed' + )} /> ); @@ -92,6 +101,9 @@ export const DefaultObjectFunction = () => ( eventsFunctionsContainer={testProject.testEventsBasedObject.getEventsFunctions()} eventsFunctionsExtension={testProject.testEventsFunctionsExtension} onParametersOrGroupsUpdated={action('Parameters or groups were updated')} + onFunctionParameterWillBeRenamed={action( + 'onFunctionParameterWillBeRenamed' + )} /> ); From c0021acfddf6d7d59e8a5cf2bea516eed80fd9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sun, 1 Dec 2024 17:46:15 +0100 Subject: [PATCH 20/26] Put back a condition removed by mistake. --- Core/GDCore/IDE/Events/EventsParameterReplace.cpp | 3 +-- Core/GDCore/IDE/Events/EventsParameterReplacer.h | 2 +- Core/GDCore/IDE/Events/EventsRefactorer.cpp | 12 ++++++++---- Core/GDCore/IDE/WholeProjectRefactorer.h | 7 +++++++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Core/GDCore/IDE/Events/EventsParameterReplace.cpp b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp index 8d1150ea986b..4bde1702b27c 100644 --- a/Core/GDCore/IDE/Events/EventsParameterReplace.cpp +++ b/Core/GDCore/IDE/Events/EventsParameterReplace.cpp @@ -29,8 +29,7 @@ namespace gd { /** - * \brief Go through the nodes and rename parameters, - * or signal if the instruction must be renamed if a removed property is used. + * \brief Go through the nodes and rename parameters. * * \see gd::ExpressionParser2 */ diff --git a/Core/GDCore/IDE/Events/EventsParameterReplacer.h b/Core/GDCore/IDE/Events/EventsParameterReplacer.h index c11bfaaf79f0..3e233f617438 100644 --- a/Core/GDCore/IDE/Events/EventsParameterReplacer.h +++ b/Core/GDCore/IDE/Events/EventsParameterReplacer.h @@ -23,7 +23,7 @@ class Platform; namespace gd { /** * \brief Replace in expressions and in parameters of actions or conditions, - * references to the name of a property by another. + * references to the name of a parameter by another. * * \ingroup IDE */ diff --git a/Core/GDCore/IDE/Events/EventsRefactorer.cpp b/Core/GDCore/IDE/Events/EventsRefactorer.cpp index 33a9a2ab5f65..058d2fa4c3e4 100644 --- a/Core/GDCore/IDE/Events/EventsRefactorer.cpp +++ b/Core/GDCore/IDE/Events/EventsRefactorer.cpp @@ -55,11 +55,15 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker { const gd::String &rootType, gd::ExpressionNode &node, const gd::String &objectName, const gd::String &objectNewName) { - ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, - objectName, objectNewName); - node.Visit(renamer); + if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, + rootType, node)) { + ExpressionObjectRenamer renamer(platform, projectScopedContainers, + rootType, objectName, objectNewName); + node.Visit(renamer); - return renamer.HasDoneRenaming(); + return renamer.HasDoneRenaming(); + } + return false; } bool HasDoneRenaming() const { return hasDoneRenaming; } diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index 63749f7844c2..baf77487af8b 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -176,6 +176,13 @@ class GD_CORE_API WholeProjectRefactorer { const gd::String& oldFunctionName, const gd::String& newFunctionName); + /** + * \brief Refactor the function **before** a parameter is renamed. + * + * \warning Do the renaming of the specified parameter after calling this. + * This is because the function is expected to have its old name for the + * refactoring. + */ static void RenameParameter(gd::Project &project, gd::ProjectScopedContainers &projectScopedContainers, From 72c4d626425e4d2496b19e6ef7f1f3e7bb414479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Fri, 6 Dec 2024 16:45:13 +0100 Subject: [PATCH 21/26] Add tests for parameter renamed in variable parameters. --- Core/tests/WholeProjectRefactorer.cpp | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index e5d1b2d143f6..bd49a9512639 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -2446,6 +2446,68 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyExtension::GetVariableAsNumber(MyVariable.MyChild[MyRenamedParameter])"); } + SECTION("(Free) number parameter renamed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + auto &instruction = CreateNumberVariableGetterCondition( + project, eventsFunction.GetEvents(), "MyParameter", "123"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter", "MyRenamedParameter"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedParameter"); + } + + SECTION("(Free) number parameter renamed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + auto &instruction = CreateNumberVariableGetterCondition( + project, eventsFunction.GetEvents(), "MyParameter", "123"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::RenameParameter( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter", "MyRenamedParameter"); + + REQUIRE(instruction.GetParameter(0).GetPlainString() == + "MyRenamedParameter"); + } + SECTION("(Free) number parameter not renamed (in variable parameter)") { gd::Project project; gd::Platform platform; From 42eae7a4578d235077bad1b0b8a29503c1736018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 7 Dec 2024 16:21:59 +0100 Subject: [PATCH 22/26] Add a refactoring operation for parameter type change. --- Core/GDCore/IDE/WholeProjectRefactorer.cpp | 17 +++++ Core/GDCore/IDE/WholeProjectRefactorer.h | 10 +++ Core/tests/WholeProjectRefactorer.cpp | 63 +++++++++++++++++++ GDevelop.js/Bindings/Bindings.idl | 6 ++ GDevelop.js/Bindings/Wrapper.cpp | 1 + GDevelop.js/types.d.ts | 1 + GDevelop.js/types/gdwholeprojectrefactorer.js | 1 + .../EventsFunctionParametersEditor.js | 15 ++++- .../index.js | 6 ++ .../EventsFunctionsExtensionEditor/index.js | 21 +++++++ .../EventsFunctionExtractorDialog.js | 3 + ...entsFunctionConfigurationEditor.stories.js | 12 ++++ 12 files changed, 153 insertions(+), 3 deletions(-) diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.cpp b/Core/GDCore/IDE/WholeProjectRefactorer.cpp index 0ebb1c8370ba..67e7317522ef 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.cpp +++ b/Core/GDCore/IDE/WholeProjectRefactorer.cpp @@ -862,6 +862,23 @@ void WholeProjectRefactorer::RenameParameter( } } +void WholeProjectRefactorer::ChangeParameterType( + gd::Project &project, gd::ProjectScopedContainers &projectScopedContainers, + gd::EventsFunction &eventsFunction, + const gd::ObjectsContainer ¶meterObjectsContainer, + const gd::String ¶meterName) { + std::unordered_set typeChangedPropertyNames; + typeChangedPropertyNames.insert(parameterName); + gd::VariablesContainer propertyVariablesContainer( + gd::VariablesContainer::SourceType::Properties); + gd::EventsVariableInstructionTypeSwitcher + eventsVariableInstructionTypeSwitcher(project.GetCurrentPlatform(), + typeChangedPropertyNames, + propertyVariablesContainer); + eventsVariableInstructionTypeSwitcher.Launch(eventsFunction.GetEvents(), + projectScopedContainers); +} + void WholeProjectRefactorer::MoveEventsFunctionParameter( gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension, diff --git a/Core/GDCore/IDE/WholeProjectRefactorer.h b/Core/GDCore/IDE/WholeProjectRefactorer.h index baf77487af8b..b9f4c96bd25c 100644 --- a/Core/GDCore/IDE/WholeProjectRefactorer.h +++ b/Core/GDCore/IDE/WholeProjectRefactorer.h @@ -191,6 +191,16 @@ class GD_CORE_API WholeProjectRefactorer { const gd::String &oldParameterName, const gd::String &newParameterName); + /** + * \brief Refactor the function **after** a parameter has changed of type. + */ + static void + ChangeParameterType(gd::Project &project, + gd::ProjectScopedContainers &projectScopedContainers, + gd::EventsFunction &eventsFunction, + const gd::ObjectsContainer ¶meterObjectsContainer, + const gd::String ¶meterName); + /** * \brief Refactor the project **before** an events function parameter * is moved. diff --git a/Core/tests/WholeProjectRefactorer.cpp b/Core/tests/WholeProjectRefactorer.cpp index bd49a9512639..d38021ade18e 100644 --- a/Core/tests/WholeProjectRefactorer.cpp +++ b/Core/tests/WholeProjectRefactorer.cpp @@ -2508,6 +2508,69 @@ TEST_CASE("WholeProjectRefactorer", "[common]") { "MyRenamedParameter"); } + SECTION("(Free) parameter type changed (in variable setter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + // The property was of type "string". + auto &instruction = CreateStringVariableSetterAction( + project, eventsFunction.GetEvents(), "MyParameter", "123"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::ChangeParameterType( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter"); + + REQUIRE(instruction.GetType() == "SetNumberVariable"); + } + + SECTION("(Free) parameter type changed (in variable getter)") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project); + + auto &eventsFunction = + eventsExtension.InsertNewEventsFunction("MyFreeEventsFunction", 0); + eventsFunction.GetParameters() + .AddNewParameter("MyParameter") + .GetValueTypeMetadata() + .SetName("number"); + // The property was of type "string". + auto &instruction = CreateStringVariableGetterCondition( + project, eventsFunction.GetEvents(), "MyParameter", "123"); + + gd::ObjectsContainer parametersObjectsContainer( + gd::ObjectsContainer::SourceType::Function); + gd::VariablesContainer parameterVariablesContainer( + gd::VariablesContainer::SourceType::Parameters); + auto projectScopedContainers = gd::ProjectScopedContainers:: + MakeNewProjectScopedContainersForFreeEventsFunction( + project, eventsExtension, eventsFunction, + parametersObjectsContainer, parameterVariablesContainer); + gd::WholeProjectRefactorer::ChangeParameterType( + project, projectScopedContainers, eventsFunction, + parametersObjectsContainer, "MyParameter"); + + REQUIRE(instruction.GetType() == "NumberVariable"); + } + + SECTION("(Free) number parameter not renamed (in variable parameter)") { gd::Project project; gd::Platform platform; diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 1ca7099c16e7..37ae6a7a5dd1 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -2536,6 +2536,12 @@ interface WholeProjectRefactorer { [Const, Ref] ObjectsContainer parameterObjectsContainer, [Const] DOMString oldName, [Const] DOMString newName); + void STATIC_ChangeParameterType( + [Ref] Project project, + [Ref] ProjectScopedContainers projectScopedContainers, + [Ref] EventsFunction eventsFunction, + [Const, Ref] ObjectsContainer parameterObjectsContainer, + [Const] DOMString parameterName); void STATIC_MoveEventsFunctionParameter( [Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, diff --git a/GDevelop.js/Bindings/Wrapper.cpp b/GDevelop.js/Bindings/Wrapper.cpp index a7ba3b1bb678..0a6f131fdfa3 100644 --- a/GDevelop.js/Bindings/Wrapper.cpp +++ b/GDevelop.js/Bindings/Wrapper.cpp @@ -728,6 +728,7 @@ typedef ExtensionAndMetadata ExtensionAndExpressionMetadata; #define STATIC_RenameBehaviorEventsFunction RenameBehaviorEventsFunction #define STATIC_RenameObjectEventsFunction RenameObjectEventsFunction #define STATIC_RenameParameter RenameParameter +#define STATIC_ChangeParameterType ChangeParameterType #define STATIC_MoveEventsFunctionParameter MoveEventsFunctionParameter #define STATIC_MoveBehaviorEventsFunctionParameter \ MoveBehaviorEventsFunctionParameter diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index 8e2c31a0c43b..779b76beff08 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -1907,6 +1907,7 @@ export class WholeProjectRefactorer extends EmscriptenObject { static renameBehaviorEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, oldName: string, newName: string): void; static renameObjectEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, oldName: string, newName: string): void; static renameParameter(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, oldName: string, newName: string): void; + static changeParameterType(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, parameterName: string): void; static moveEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, functionName: string, oldIndex: number, newIndex: number): void; static moveBehaviorEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, functionName: string, oldIndex: number, newIndex: number): void; static moveObjectEventsFunctionParameter(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; diff --git a/GDevelop.js/types/gdwholeprojectrefactorer.js b/GDevelop.js/types/gdwholeprojectrefactorer.js index 8162b5b55926..c96ec85be99b 100644 --- a/GDevelop.js/types/gdwholeprojectrefactorer.js +++ b/GDevelop.js/types/gdwholeprojectrefactorer.js @@ -10,6 +10,7 @@ declare class gdWholeProjectRefactorer { static renameBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void; static renameObjectEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string): void; static renameParameter(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, oldName: string, newName: string): void; + static changeParameterType(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, parameterName: string): void; static moveEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, functionName: string, oldIndex: number, newIndex: number): void; static moveBehaviorEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, functionName: string, oldIndex: number, newIndex: number): void; static moveObjectEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, functionName: string, oldIndex: number, newIndex: number): void; diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js index d7109b32e261..9c25de2984be 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/EventsFunctionParametersEditor.js @@ -129,6 +129,10 @@ type Props = {| oldName: string, newName: string ) => void, + onFunctionParameterChangedOfType: ( + eventsFunction: gdEventsFunction, + parameterName: string + ) => void, |}; export const EventsFunctionParametersEditor = ({ @@ -146,6 +150,7 @@ export const EventsFunctionParametersEditor = ({ onMoveBehaviorEventsParameter, onMoveObjectEventsParameter, onFunctionParameterWillBeRenamed, + onFunctionParameterChangedOfType, }: Props) => { const scrollView = React.useRef(null); const [ @@ -788,9 +793,13 @@ export const EventsFunctionParametersEditor = ({ isTypeSelectorShown={isParameterTypeShown( i )} - onTypeUpdated={() => - onParametersUpdated() - } + onTypeUpdated={() => { + onFunctionParameterChangedOfType( + eventsFunction, + parameter.getName() + ); + onParametersUpdated(); + }} getLastObjectParameterObjectType={() => getLastObjectParameterObjectType( parameters, diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js index b65fe5a226a9..9877f7819fa6 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor/index.js @@ -57,6 +57,10 @@ type Props = {| oldName: string, newName: string ) => void, + onFunctionParameterChangedOfType: ( + eventsFunction: gdEventsFunction, + parameterName: string + ) => void, unsavedChanges?: ?UnsavedChanges, getFunctionGroupNames?: () => string[], |}; @@ -156,6 +160,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< eventsFunctionsContainer, eventsFunctionsExtension, onFunctionParameterWillBeRenamed, + onFunctionParameterChangedOfType, } = this.props; const hasLegacyFunctionObjectGroups = @@ -222,6 +227,7 @@ export default class EventsFunctionConfigurationEditor extends React.Component< onMoveBehaviorEventsParameter={onMoveBehaviorEventsParameter} onMoveObjectEventsParameter={onMoveObjectEventsParameter} onFunctionParameterWillBeRenamed={onFunctionParameterWillBeRenamed} + onFunctionParameterChangedOfType={onFunctionParameterChangedOfType} key={eventsFunction ? eventsFunction.ptr : null} /> ) : null} diff --git a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js index afc8658bc84e..7c54890729b1 100644 --- a/newIDE/app/src/EventsFunctionsExtensionEditor/index.js +++ b/newIDE/app/src/EventsFunctionsExtensionEditor/index.js @@ -1078,6 +1078,24 @@ export default class EventsFunctionsExtensionEditor extends React.Component< ); }; + _onFunctionParameterChangedOfType = ( + eventsFunction: gdEventsFunction, + parameterName: string + ) => { + if (!this._projectScopedContainersAccessor) { + return; + } + const projectScopedContainers = this._projectScopedContainersAccessor.get(); + const { project } = this.props; + gd.WholeProjectRefactorer.changeParameterType( + project, + projectScopedContainers, + eventsFunction, + this._objectsContainer, + parameterName + ); + }; + _editOptions = (open: boolean = true) => { this.setState({ editOptionsDialogOpen: open, @@ -1370,6 +1388,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component< onFunctionParameterWillBeRenamed={ this._onFunctionParameterWillBeRenamed } + onFunctionParameterChangedOfType={ + this._onFunctionParameterChangedOfType + } unsavedChanges={this.props.unsavedChanges} getFunctionGroupNames={this._getFunctionGroupNames} /> diff --git a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js index 1a828f5e48db..d8fc89b92b74 100644 --- a/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js +++ b/newIDE/app/src/EventsSheet/EventsFunctionExtractor/EventsFunctionExtractorDialog.js @@ -329,6 +329,9 @@ export default class EventsFunctionExtractorDialog extends React.Component< onFunctionParameterWillBeRenamed={() => { // Won't happen as the editor is freezed. }} + onFunctionParameterChangedOfType={() => { + // Won't happen as the editor is freezed. + }} freezeParameters /> )} diff --git a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js index e8b8a4f72bed..4367158e2899 100644 --- a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/EventsFunctionConfigurationEditor.stories.js @@ -35,6 +35,9 @@ export const DefaultFreeFunction = () => ( onFunctionParameterWillBeRenamed={action( 'onFunctionParameterWillBeRenamed' )} + onFunctionParameterChangedOfType={action( + 'onFunctionParameterChangedOfType' + )} /> ); @@ -58,6 +61,9 @@ export const DefaultBehaviorFunction = () => ( onFunctionParameterWillBeRenamed={action( 'onFunctionParameterWillBeRenamed' )} + onFunctionParameterChangedOfType={action( + 'onFunctionParameterChangedOfType' + )} /> ); @@ -81,6 +87,9 @@ export const DefaultBehaviorLifecycleFunction = () => ( onFunctionParameterWillBeRenamed={action( 'onFunctionParameterWillBeRenamed' )} + onFunctionParameterChangedOfType={action( + 'onFunctionParameterChangedOfType' + )} /> ); @@ -104,6 +113,9 @@ export const DefaultObjectFunction = () => ( onFunctionParameterWillBeRenamed={action( 'onFunctionParameterWillBeRenamed' )} + onFunctionParameterChangedOfType={action( + 'onFunctionParameterChangedOfType' + )} /> ); From 4da978377aa9b0df64cf827a4e60ae26812a086b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 7 Dec 2024 16:45:27 +0100 Subject: [PATCH 23/26] Fix a regression in code generation for local variables in behavior functions. --- Core/GDCore/Project/VariablesContainersList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GDCore/Project/VariablesContainersList.cpp b/Core/GDCore/Project/VariablesContainersList.cpp index 3b07eeac160c..ef724e3a2a0f 100644 --- a/Core/GDCore/Project/VariablesContainersList.cpp +++ b/Core/GDCore/Project/VariablesContainersList.cpp @@ -84,7 +84,7 @@ VariablesContainersList VariablesContainersList:: parameterVariablesContainer); variablesContainersList.Push(parameterVariablesContainer); - variablesContainersList.firstLocalVariableContainerIndex = 4; + variablesContainersList.firstLocalVariableContainerIndex = 5; return variablesContainersList; } From f5180d14e60503dd07794faf9c5be1987a43764f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 7 Dec 2024 18:01:11 +0100 Subject: [PATCH 24/26] Add tests for free functions. --- .../TestUtils/CodeGenerationHelpers.js | 1 + ...eFunctionCodeGenerationIntegrationTests.js | 273 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 GDevelop.js/__tests__/GDJSFreeFunctionCodeGenerationIntegrationTests.js diff --git a/GDevelop.js/TestUtils/CodeGenerationHelpers.js b/GDevelop.js/TestUtils/CodeGenerationHelpers.js index e8963d014ff1..34dabcbbd150 100644 --- a/GDevelop.js/TestUtils/CodeGenerationHelpers.js +++ b/GDevelop.js/TestUtils/CodeGenerationHelpers.js @@ -458,6 +458,7 @@ function generateCompiledEventsForLayout(gd, project, layout, logCode = false) { module.exports = { generateCompiledEventsForEventsFunction, + generateCompiledEventsForEventsFunctionWithContext, generateCompiledEventsFromSerializedEvents, generateCompiledEventsFunctionFromSerializedEvents, generateCompiledEventsForSerializedEventsBasedExtension, diff --git a/GDevelop.js/__tests__/GDJSFreeFunctionCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSFreeFunctionCodeGenerationIntegrationTests.js new file mode 100644 index 000000000000..0aa52564326b --- /dev/null +++ b/GDevelop.js/__tests__/GDJSFreeFunctionCodeGenerationIntegrationTests.js @@ -0,0 +1,273 @@ +const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); +const { + generateCompiledEventsForEventsFunctionWithContext, +} = require('../TestUtils/CodeGenerationHelpers.js'); +const { makeMinimalGDJSMock } = require('../TestUtils/GDJSMocks.js'); +const { + generateCompiledEventsForEventsBasedBehavior, +} = require('../TestUtils/CodeGenerationHelpers.js'); + +describe('libGD.js - GDJS Free Function Code Generation integration tests', function () { + let gd = null; + beforeAll(async () => { + gd = await initializeGDevelopJs(); + }); + + it('Can use a parameter in a variable condition', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyParameter', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsFunctionsExtension.insertNewEventsFunction( + 'MyFunction', + 0 + ); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyParameter', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { gdjs, runtimeScene, runCompiledEvents } = generatedFreeFunction( + gd, + project, + eventsFunctionsExtension, + eventsFunction, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + runCompiledEvents(gdjs, runtimeScene, [123]); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a parameter in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + + // Extension scene variable with the same name as the parameter. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsFunctionsExtension.insertNewEventsFunction( + 'MyFunction', + 0 + ); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { gdjs, runtimeScene, runCompiledEvents } = generatedFreeFunction( + gd, + project, + eventsFunctionsExtension, + eventsFunction, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + runCompiledEvents(gdjs, runtimeScene, [123]); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); + + it('Can use a local variable in a variable condition (with name collisions)', () => { + const project = new gd.ProjectHelper.createNewGDJSProject(); + const eventsFunctionsExtension = project.insertNewEventsFunctionsExtension( + 'MyExtension', + 0 + ); + + // Extension scene variable with the same name as the local variable. + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyIdentifier', 0) + .setValue(222); + + eventsFunctionsExtension + .getSceneVariables() + .insertNew('MyVariable', 0) + .setValue(0); + + const eventsSerializerElement = gd.Serializer.fromJSObject([ + { + type: 'BuiltinCommonInstructions::Standard', + variables: [ + { + name: 'MyIdentifier', + type: 'number', + value: 123, + }, + ], + conditions: [ + { + type: { value: 'NumberVariable' }, + parameters: ['MyIdentifier', '=', '123'], + }, + ], + actions: [ + { + type: { value: 'SetNumberVariable' }, + parameters: ['MyVariable', '=', '456'], + }, + ], + }, + ]); + const eventsFunction = eventsFunctionsExtension.insertNewEventsFunction( + 'MyFunction', + 0 + ); + eventsFunction + .getEvents() + .unserializeFrom(project, eventsSerializerElement); + // Parameter with the same name as the local variable. + const parameter = eventsFunction + .getParameters() + .insertNewParameter( + 'MyIdentifier', + eventsFunction.getParameters().getParametersCount() + ); + parameter.setType('number'); + + const { gdjs, runtimeScene, runCompiledEvents } = generatedFreeFunction( + gd, + project, + eventsFunctionsExtension, + eventsFunction, + { logCode: false } + ); + + // Check the default value is set. + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(0); + + runCompiledEvents(gdjs, runtimeScene, [333]); + expect( + runtimeScene + .getVariablesForExtension('MyExtension') + .get('MyVariable') + .getAsNumber() + ).toBe(456); + }); +}); + +function generatedFreeFunction( + gd, + project, + eventsFunctionsExtension, + eventsFunction, + options = {} +) { + const serializedProjectElement = new gd.SerializerElement(); + project.serializeTo(serializedProjectElement); + const serializedSceneElement = new gd.SerializerElement(); + const scene = project.insertNewLayout('MyScene', 0); + scene.serializeTo(serializedSceneElement); + const { gdjs, runtimeScene } = makeMinimalGDJSMock({ + gameData: JSON.parse(gd.Serializer.toJSON(serializedProjectElement)), + sceneData: JSON.parse(gd.Serializer.toJSON(serializedSceneElement)), + }); + + const runCompiledEvents = generateCompiledEventsForEventsFunctionWithContext( + gd, + project, + eventsFunctionsExtension, + eventsFunction, + options.logCode + ); + serializedProjectElement.delete(); + serializedSceneElement.delete(); + project.delete(); + + return { gdjs, runtimeScene, runCompiledEvents }; +} From 6944be77ad14fc3a128bab81d83d9e1c3390bcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Mon, 9 Dec 2024 00:28:19 +0100 Subject: [PATCH 25/26] Add a line break in the generated code. --- GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp index df849abf448b..8535d79ef511 100644 --- a/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp +++ b/GDJS/GDJS/Events/CodeGeneration/EventsCodeGenerator.cpp @@ -1541,7 +1541,7 @@ gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting( property.GetName()) : BehaviorCodeGenerator::GetBehaviorSharedPropertySetterName( property.GetName())) + - "(" + operandCode + ")"; + "(" + operandCode + ")\n"; return propertySetterCode; } From de90712e5ef9c9a1a9c1153643352478ae9b7f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Fri, 13 Dec 2024 13:06:06 +0100 Subject: [PATCH 26/26] Fix boolean variable conditions when the value is neither True or False. --- .../Extensions/Builtin/VariablesExtension.cpp | 2 +- GDevelop.js/TestUtils/GDJSMocks.js | 8 +++ ...nVariableCodeGenerationIntegrationTests.js | 4 +- ...tVariableCodeGenerationIntegrationTests.js | 2 +- ...eVariableCodeGenerationIntegrationTests.js | 71 ++++++++++++++++++- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp index 95d7839395e5..5aaa1d816039 100644 --- a/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp +++ b/GDJS/GDJS/Extensions/Builtin/VariablesExtension.cpp @@ -102,7 +102,7 @@ VariablesExtension::VariablesExtension() { codeGenerator, context, "variable", instruction.GetParameters()[0].GetPlainString()); bool isOperandTrue = - instruction.GetParameters()[1].GetPlainString() != "False"; + instruction.GetParameters()[1].GetPlainString() == "True"; const auto variablesContainersList = codeGenerator.GetProjectScopedContainers().GetVariablesContainersList(); diff --git a/GDevelop.js/TestUtils/GDJSMocks.js b/GDevelop.js/TestUtils/GDJSMocks.js index f680b20f0f31..62a886f8b635 100644 --- a/GDevelop.js/TestUtils/GDJSMocks.js +++ b/GDevelop.js/TestUtils/GDJSMocks.js @@ -434,6 +434,14 @@ class RuntimeObject { return variable.getAsNumber(); } + static getVariableString(variable) { + return variable.getAsString(); + } + + getVariableString(variable) { + return variable.getAsString(); + } + static getVariableBoolean(variable) { return variable.getAsBoolean(); } diff --git a/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js index 6aa0a4767c43..d80496f30636 100644 --- a/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSExtensionVariableCodeGenerationIntegrationTests.js @@ -211,7 +211,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () { const runtimeScene = generateAndRunVariableAffectationWithConditions([ { type: { inverted: false, value: 'BooleanVariable' }, - parameters: ['MyVariable'], + parameters: ['MyVariable', 'True', ''], }, ]); expect( @@ -229,7 +229,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () { .setString('Same value'); const runtimeScene = generateAndRunVariableAffectationWithConditions([ { - type: { inverted: false, value: 'BooleanVariable' }, + type: { inverted: false, value: 'StringVariable' }, parameters: ['MyVariable', '=', '"Same value"'], }, ]); diff --git a/GDevelop.js/__tests__/GDJSSceneObjectVariableCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSSceneObjectVariableCodeGenerationIntegrationTests.js index 96407208ab61..3673c35c73f0 100644 --- a/GDevelop.js/__tests__/GDJSSceneObjectVariableCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSSceneObjectVariableCodeGenerationIntegrationTests.js @@ -243,7 +243,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () { object.getVariables().insertNew('MyVariable', 0).setString('Same value'); const runtimeScene = generateAndRunVariableAffectationWithConditions([ { - type: { inverted: false, value: 'BooleanObjectVariable' }, + type: { inverted: false, value: 'StringObjectVariable' }, parameters: ['MyObject', 'MyVariable', '=', '"Same value"'], }, ]); diff --git a/GDevelop.js/__tests__/GDJSSceneVariableCodeGenerationIntegrationTests.js b/GDevelop.js/__tests__/GDJSSceneVariableCodeGenerationIntegrationTests.js index 00af220fb163..1457bdcf8c02 100644 --- a/GDevelop.js/__tests__/GDJSSceneVariableCodeGenerationIntegrationTests.js +++ b/GDevelop.js/__tests__/GDJSSceneVariableCodeGenerationIntegrationTests.js @@ -181,12 +181,77 @@ describe('libGD.js - GDJS Code Generation integration tests', function () { ).toBe(1); }); - it('can generate a scene boolean variable condition that is true', function () { + it('can generate a scene boolean variable condition that checks true', function () { scene.getVariables().insertNew('MyVariable', 0).setBool(true); const runtimeScene = generateAndRunVariableAffectationWithConditions([ { type: { inverted: false, value: 'BooleanVariable' }, - parameters: ['MyVariable'], + parameters: ['MyVariable', 'True', ''], + }, + ]); + expect( + runtimeScene.getVariables().get('SuccessVariable').getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that checks true (inverted)', function () { + scene.getVariables().insertNew('MyVariable', 0).setBool(false); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: true, value: 'BooleanVariable' }, + parameters: ['MyVariable', 'True', ''], + }, + ]); + expect( + runtimeScene.getVariables().get('SuccessVariable').getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that checks false', function () { + scene.getVariables().insertNew('MyVariable', 0).setBool(false); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'BooleanVariable' }, + parameters: ['MyVariable', 'False', ''], + }, + ]); + expect( + runtimeScene.getVariables().get('SuccessVariable').getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that checks false (inverted)', function () { + scene.getVariables().insertNew('MyVariable', 0).setBool(true); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: true, value: 'BooleanVariable' }, + parameters: ['MyVariable', 'False', ''], + }, + ]); + expect( + runtimeScene.getVariables().get('SuccessVariable').getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that checks false (defaulted from an empty string)', function () { + scene.getVariables().insertNew('MyVariable', 0).setBool(false); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: false, value: 'BooleanVariable' }, + parameters: ['MyVariable', '', ''], + }, + ]); + expect( + runtimeScene.getVariables().get('SuccessVariable').getAsNumber() + ).toBe(1); + }); + + it('can generate a scene boolean variable condition that checks false (defaulted from an empty string, inverted)', function () { + scene.getVariables().insertNew('MyVariable', 0).setBool(true); + const runtimeScene = generateAndRunVariableAffectationWithConditions([ + { + type: { inverted: true, value: 'BooleanVariable' }, + parameters: ['MyVariable', '', ''], }, ]); expect( @@ -198,7 +263,7 @@ describe('libGD.js - GDJS Code Generation integration tests', function () { scene.getVariables().insertNew('MyVariable', 0).setString('Same value'); const runtimeScene = generateAndRunVariableAffectationWithConditions([ { - type: { inverted: false, value: 'BooleanVariable' }, + type: { inverted: false, value: 'StringVariable' }, parameters: ['MyVariable', '=', '"Same value"'], }, ]);