diff --git a/Core/GDCore/Extensions/Metadata/MetadataProvider.cpp b/Core/GDCore/Extensions/Metadata/MetadataProvider.cpp index 72cf108e7ae8..9720becba0a7 100644 --- a/Core/GDCore/Extensions/Metadata/MetadataProvider.cpp +++ b/Core/GDCore/Extensions/Metadata/MetadataProvider.cpp @@ -1,477 +1,365 @@ -/* - * GDevelop Core - * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights - * reserved. This project is released under the MIT License. - */ -#include "GDCore/Extensions/Metadata/MetadataProvider.h" - -#include - -#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" -#include "GDCore/Extensions/Metadata/EffectMetadata.h" -#include "GDCore/Extensions/Metadata/InstructionMetadata.h" -#include "GDCore/Extensions/Metadata/ObjectMetadata.h" -#include "GDCore/Extensions/Platform.h" -#include "GDCore/Extensions/PlatformExtension.h" -#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior -#include "GDCore/Project/ObjectsContainersList.h" -#include "GDCore/String.h" -#include "GDCore/Events/Parsers/ExpressionParser2.h" - -using namespace std; - -namespace gd { - -gd::BehaviorMetadata MetadataProvider::badBehaviorMetadata; -gd::ObjectMetadata MetadataProvider::badObjectInfo; -gd::EffectMetadata MetadataProvider::badEffectMetadata; -gd::InstructionMetadata MetadataProvider::badInstructionMetadata; -gd::ExpressionMetadata MetadataProvider::badExpressionMetadata; -gd::PlatformExtension MetadataProvider::badExtension; - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndBehaviorMetadata(const gd::Platform& platform, - gd::String behaviorType) { - for (auto& extension : platform.GetAllPlatformExtensions()) { - if (extension->HasBehavior(behaviorType)) - return ExtensionAndMetadata( - *extension, extension->GetBehaviorMetadata(behaviorType)); - } - - return ExtensionAndMetadata(badExtension, badBehaviorMetadata); -} - -const BehaviorMetadata& MetadataProvider::GetBehaviorMetadata( - const gd::Platform& platform, gd::String behaviorType) { - return GetExtensionAndBehaviorMetadata(platform, behaviorType).GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndObjectMetadata(const gd::Platform& platform, - gd::String objectType) { - for (auto& extension : platform.GetAllPlatformExtensions()) { - auto objectsTypes = extension->GetExtensionObjectsTypes(); - for (std::size_t j = 0; j < objectsTypes.size(); ++j) { - if (objectsTypes[j] == objectType) - return ExtensionAndMetadata( - *extension, extension->GetObjectMetadata(objectType)); - } - } - - return ExtensionAndMetadata(badExtension, badObjectInfo); -} - -const ObjectMetadata& MetadataProvider::GetObjectMetadata( - const gd::Platform& platform, gd::String objectType) { - return GetExtensionAndObjectMetadata(platform, objectType).GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndEffectMetadata(const gd::Platform& platform, - gd::String type) { - for (auto& extension : platform.GetAllPlatformExtensions()) { - auto objectsTypes = extension->GetExtensionEffectTypes(); - for (std::size_t j = 0; j < objectsTypes.size(); ++j) { - if (objectsTypes[j] == type) - return ExtensionAndMetadata( - *extension, extension->GetEffectMetadata(type)); - } - } - - return ExtensionAndMetadata(badExtension, badEffectMetadata); -} - -const EffectMetadata& MetadataProvider::GetEffectMetadata( - const gd::Platform& platform, gd::String objectType) { - return GetExtensionAndEffectMetadata(platform, objectType).GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndActionMetadata(const gd::Platform& platform, - gd::String actionType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& allActions = extension->GetAllActions(); - if (allActions.find(actionType) != allActions.end()) - return ExtensionAndMetadata( - *extension, allActions.find(actionType)->second); - - const auto& objects = extension->GetExtensionObjectsTypes(); - for (const gd::String& extObjectType : objects) { - const auto& allObjectsActions = - extension->GetAllActionsForObject(extObjectType); - if (allObjectsActions.find(actionType) != allObjectsActions.end()) - return ExtensionAndMetadata( - *extension, allObjectsActions.find(actionType)->second); - } - - const auto& autos = extension->GetBehaviorsTypes(); - for (std::size_t j = 0; j < autos.size(); ++j) { - const auto& allAutosActions = - extension->GetAllActionsForBehavior(autos[j]); - if (allAutosActions.find(actionType) != allAutosActions.end()) - return ExtensionAndMetadata( - *extension, allAutosActions.find(actionType)->second); - } - } - - return ExtensionAndMetadata(badExtension, - badInstructionMetadata); -} - -const gd::InstructionMetadata& MetadataProvider::GetActionMetadata( - const gd::Platform& platform, gd::String actionType) { - return GetExtensionAndActionMetadata(platform, actionType).GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndConditionMetadata(const gd::Platform& platform, - gd::String conditionType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& allConditions = extension->GetAllConditions(); - if (allConditions.find(conditionType) != allConditions.end()) - return ExtensionAndMetadata( - *extension, allConditions.find(conditionType)->second); - - const auto& objects = extension->GetExtensionObjectsTypes(); - for (const gd::String& extObjectType : objects) { - const auto& allObjectsConditions = - extension->GetAllConditionsForObject(extObjectType); - if (allObjectsConditions.find(conditionType) != allObjectsConditions.end()) - return ExtensionAndMetadata( - *extension, allObjectsConditions.find(conditionType)->second); - } - - const auto& autos = extension->GetBehaviorsTypes(); - for (std::size_t j = 0; j < autos.size(); ++j) { - const auto& allAutosConditions = - extension->GetAllConditionsForBehavior(autos[j]); - if (allAutosConditions.find(conditionType) != allAutosConditions.end()) - return ExtensionAndMetadata( - *extension, allAutosConditions.find(conditionType)->second); - } - } - - return ExtensionAndMetadata(badExtension, - badInstructionMetadata); -} - -const gd::InstructionMetadata& MetadataProvider::GetConditionMetadata( - const gd::Platform& platform, gd::String conditionType) { - return GetExtensionAndConditionMetadata(platform, conditionType) - .GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndObjectExpressionMetadata( - const gd::Platform& platform, gd::String objectType, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& objects = extension->GetExtensionObjectsTypes(); - if (find(objects.begin(), objects.end(), objectType) != objects.end()) { - const auto& allObjectExpressions = - extension->GetAllExpressionsForObject(objectType); - if (allObjectExpressions.find(exprType) != allObjectExpressions.end()) - return ExtensionAndMetadata( - *extension, allObjectExpressions.find(exprType)->second); - } - } - - // Then check base - for (auto& extension : extensions) { - const auto& allObjectExpressions = - extension->GetAllExpressionsForObject(""); - if (allObjectExpressions.find(exprType) != allObjectExpressions.end()) - return ExtensionAndMetadata( - *extension, allObjectExpressions.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& MetadataProvider::GetObjectExpressionMetadata( - const gd::Platform& platform, gd::String objectType, gd::String exprType) { - return GetExtensionAndObjectExpressionMetadata(platform, objectType, exprType) - .GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndBehaviorExpressionMetadata( - const gd::Platform& platform, gd::String autoType, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - if (extension->HasBehavior(autoType)) { - const auto& allAutoExpressions = - extension->GetAllExpressionsForBehavior(autoType); - if (allAutoExpressions.find(exprType) != allAutoExpressions.end()) - return ExtensionAndMetadata( - *extension, allAutoExpressions.find(exprType)->second); - } - } - - // Then check base - for (auto& extension : extensions) { - const auto& allAutoExpressions = - extension->GetAllExpressionsForBehavior(""); - if (allAutoExpressions.find(exprType) != allAutoExpressions.end()) - return ExtensionAndMetadata( - *extension, allAutoExpressions.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& MetadataProvider::GetBehaviorExpressionMetadata( - const gd::Platform& platform, gd::String autoType, gd::String exprType) { - return GetExtensionAndBehaviorExpressionMetadata(platform, autoType, exprType) - .GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndExpressionMetadata( - const gd::Platform& platform, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& allExpr = extension->GetAllExpressions(); - if (allExpr.find(exprType) != allExpr.end()) - return ExtensionAndMetadata( - *extension, allExpr.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& MetadataProvider::GetExpressionMetadata( - const gd::Platform& platform, gd::String exprType) { - return GetExtensionAndExpressionMetadata(platform, exprType).GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndObjectStrExpressionMetadata( - const gd::Platform& platform, gd::String objectType, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& objects = extension->GetExtensionObjectsTypes(); - if (find(objects.begin(), objects.end(), objectType) != objects.end()) { - const auto& allObjectStrExpressions = - extension->GetAllStrExpressionsForObject(objectType); - if (allObjectStrExpressions.find(exprType) != - allObjectStrExpressions.end()) - return ExtensionAndMetadata( - *extension, allObjectStrExpressions.find(exprType)->second); - } - } - - // Then check in functions of "Base object". - for (auto& extension : extensions) { - const auto& allObjectStrExpressions = - extension->GetAllStrExpressionsForObject(""); - if (allObjectStrExpressions.find(exprType) != allObjectStrExpressions.end()) - return ExtensionAndMetadata( - *extension, allObjectStrExpressions.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& MetadataProvider::GetObjectStrExpressionMetadata( - const gd::Platform& platform, gd::String objectType, gd::String exprType) { - return GetExtensionAndObjectStrExpressionMetadata( - platform, objectType, exprType) - .GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndBehaviorStrExpressionMetadata( - const gd::Platform& platform, gd::String autoType, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - if (extension->HasBehavior(autoType)) { - const auto& allBehaviorStrExpressions = - extension->GetAllStrExpressionsForBehavior(autoType); - if (allBehaviorStrExpressions.find(exprType) != - allBehaviorStrExpressions.end()) - return ExtensionAndMetadata( - *extension, allBehaviorStrExpressions.find(exprType)->second); - } - } - - // Then check in functions of "Base object". - for (auto& extension : extensions) { - const auto& allBehaviorStrExpressions = - extension->GetAllStrExpressionsForBehavior(""); - if (allBehaviorStrExpressions.find(exprType) != - allBehaviorStrExpressions.end()) - return ExtensionAndMetadata( - *extension, allBehaviorStrExpressions.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& -MetadataProvider::GetBehaviorStrExpressionMetadata(const gd::Platform& platform, - gd::String autoType, - gd::String exprType) { - return GetExtensionAndBehaviorStrExpressionMetadata( - platform, autoType, exprType) - .GetMetadata(); -} - -ExtensionAndMetadata -MetadataProvider::GetExtensionAndStrExpressionMetadata( - const gd::Platform& platform, gd::String exprType) { - auto& extensions = platform.GetAllPlatformExtensions(); - for (auto& extension : extensions) { - const auto& allExpr = extension->GetAllStrExpressions(); - if (allExpr.find(exprType) != allExpr.end()) - return ExtensionAndMetadata( - *extension, allExpr.find(exprType)->second); - } - - return ExtensionAndMetadata(badExtension, - badExpressionMetadata); -} - -const gd::ExpressionMetadata& MetadataProvider::GetStrExpressionMetadata( - const gd::Platform& platform, gd::String exprType) { - return GetExtensionAndStrExpressionMetadata(platform, exprType).GetMetadata(); -} - -const gd::ExpressionMetadata& MetadataProvider::GetAnyExpressionMetadata( - const gd::Platform& platform, gd::String exprType) { - const auto& numberExpressionMetadata = - GetExpressionMetadata(platform, exprType); - if (&numberExpressionMetadata != &badExpressionMetadata) { - return numberExpressionMetadata; - } - const auto& stringExpressionMetadata = - GetStrExpressionMetadata(platform, exprType); - if (&stringExpressionMetadata != &badExpressionMetadata) { - return stringExpressionMetadata; - } - return badExpressionMetadata; -} - -const gd::ExpressionMetadata& MetadataProvider::GetObjectAnyExpressionMetadata( - const gd::Platform& platform, gd::String objectType, gd::String exprType) { - const auto& numberExpressionMetadata = - GetObjectExpressionMetadata(platform, objectType, exprType); - if (&numberExpressionMetadata != &badExpressionMetadata) { - return numberExpressionMetadata; - } - const auto& stringExpressionMetadata = - GetObjectStrExpressionMetadata(platform, objectType, exprType); - if (&stringExpressionMetadata != &badExpressionMetadata) { - return stringExpressionMetadata; - } - return badExpressionMetadata; -} - -const gd::ExpressionMetadata& -MetadataProvider::GetBehaviorAnyExpressionMetadata(const gd::Platform& platform, - gd::String autoType, - gd::String exprType) { - const auto& numberExpressionMetadata = - GetBehaviorExpressionMetadata(platform, autoType, exprType); - if (&numberExpressionMetadata != &badExpressionMetadata) { - return numberExpressionMetadata; - } - const auto& stringExpressionMetadata = - GetBehaviorStrExpressionMetadata(platform, autoType, exprType); - if (&stringExpressionMetadata != &badExpressionMetadata) { - return stringExpressionMetadata; - } - return badExpressionMetadata; -} - -const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata( - const gd::Platform& platform, - const gd::ObjectsContainersList &objectsContainersList, - FunctionCallNode& node) { - - if (!node.behaviorName.empty()) { - gd::String behaviorType = - objectsContainersList.GetTypeOfBehavior(node.behaviorName); - return MetadataProvider::GetBehaviorAnyExpressionMetadata( - platform, behaviorType, node.functionName); - } - else if (!node.objectName.empty()) { - gd::String objectType = - objectsContainersList.GetTypeOfObject(node.objectName); - return MetadataProvider::GetObjectAnyExpressionMetadata( - platform, objectType, node.functionName); - } - - return MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName); -} - -const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata( - const gd::Platform& platform, - const gd::ObjectsContainersList &objectsContainersList, - FunctionCallNode& functionCall, - ExpressionNode& parameter) { - int parameterIndex = -1; - for (int i = 0; i < functionCall.parameters.size(); i++) { - if (functionCall.parameters.at(i).get() == ¶meter) { - parameterIndex = i; - break; - } - } - if (parameterIndex < 0) { - return nullptr; - } - return MetadataProvider::GetFunctionCallParameterMetadata( - platform, - objectsContainersList, - functionCall, - parameterIndex); -} - -const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata( - const gd::Platform& platform, - const gd::ObjectsContainersList &objectsContainersList, - FunctionCallNode& functionCall, - int parameterIndex) { - // Search the parameter metadata index skipping invisible ones. - size_t visibleParameterIndex = 0; - size_t metadataParameterIndex = - ExpressionParser2::WrittenParametersFirstIndex( - functionCall.objectName, functionCall.behaviorName); - const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata( - platform, objectsContainersList, functionCall); - - if (IsBadExpressionMetadata(metadata)) { - return nullptr; - } - - // TODO use a badMetadata instead of a nullptr? - const gd::ParameterMetadata* parameterMetadata = nullptr; - while (metadataParameterIndex < - metadata.GetParameters().GetParametersCount()) { - if (!metadata.GetParameters().GetParameter(metadataParameterIndex) - .IsCodeOnly()) { - if (visibleParameterIndex == parameterIndex) { - parameterMetadata = - &metadata.GetParameters().GetParameter(metadataParameterIndex); - } - visibleParameterIndex++; - } - metadataParameterIndex++; - } - const int visibleParameterCount = visibleParameterIndex; - // It can be null if there are too many parameters in the expression, this text node is - // not actually linked to a parameter expected by the function call. - return parameterMetadata; -} - -MetadataProvider::~MetadataProvider() {} -MetadataProvider::MetadataProvider() {} - -} // namespace gd +/* + * GDevelop Core + * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/Extensions/Metadata/MetadataProvider.h" + +#include + +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/EffectMetadata.h" +#include "GDCore/Extensions/Metadata/InstructionMetadata.h" +#include "GDCore/Extensions/Metadata/ObjectMetadata.h" +#include "GDCore/Extensions/Metadata/PlatformMetadataIndex.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior +#include "GDCore/Project/ObjectsContainersList.h" +#include "GDCore/String.h" +#include "GDCore/Events/Parsers/ExpressionParser2.h" + +using namespace std; + +namespace gd { + +gd::BehaviorMetadata MetadataProvider::badBehaviorMetadata; +gd::ObjectMetadata MetadataProvider::badObjectInfo; +gd::EffectMetadata MetadataProvider::badEffectMetadata; +gd::InstructionMetadata MetadataProvider::badInstructionMetadata; +gd::ExpressionMetadata MetadataProvider::badExpressionMetadata; +gd::PlatformExtension MetadataProvider::badExtension; + +// The lookups below all delegate to the platform's gd::PlatformMetadataIndex, +// which resolves a type to its metadata in constant time (instead of scanning +// every extension). The index is rebuilt by the platform whenever its +// extensions change, so the returned pointers are never stale. + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndBehaviorMetadata(const gd::Platform& platform, + gd::String behaviorType) { + const auto* entry = platform.GetMetadataIndex().GetBehaviorMetadata(behaviorType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, badBehaviorMetadata); + return ExtensionAndMetadata(*entry->extension, *entry->metadata); +} + +const BehaviorMetadata& MetadataProvider::GetBehaviorMetadata( + const gd::Platform& platform, gd::String behaviorType) { + const auto* entry = platform.GetMetadataIndex().GetBehaviorMetadata(behaviorType); + return entry != nullptr ? *entry->metadata : badBehaviorMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndObjectMetadata(const gd::Platform& platform, + gd::String type) { + const auto* entry = platform.GetMetadataIndex().GetObjectMetadata(type); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, badObjectInfo); + return ExtensionAndMetadata(*entry->extension, *entry->metadata); +} + +const ObjectMetadata& MetadataProvider::GetObjectMetadata( + const gd::Platform& platform, gd::String type) { + const auto* entry = platform.GetMetadataIndex().GetObjectMetadata(type); + return entry != nullptr ? *entry->metadata : badObjectInfo; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndEffectMetadata(const gd::Platform& platform, + gd::String type) { + const auto* entry = platform.GetMetadataIndex().GetEffectMetadata(type); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, badEffectMetadata); + return ExtensionAndMetadata(*entry->extension, *entry->metadata); +} + +const EffectMetadata& MetadataProvider::GetEffectMetadata( + const gd::Platform& platform, gd::String type) { + const auto* entry = platform.GetMetadataIndex().GetEffectMetadata(type); + return entry != nullptr ? *entry->metadata : badEffectMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndActionMetadata(const gd::Platform& platform, + gd::String actionType) { + const auto* entry = platform.GetMetadataIndex().GetActionMetadata(actionType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badInstructionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::InstructionMetadata& MetadataProvider::GetActionMetadata( + const gd::Platform& platform, gd::String actionType) { + const auto* entry = platform.GetMetadataIndex().GetActionMetadata(actionType); + return entry != nullptr ? *entry->metadata : badInstructionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndConditionMetadata(const gd::Platform& platform, + gd::String conditionType) { + const auto* entry = + platform.GetMetadataIndex().GetConditionMetadata(conditionType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badInstructionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::InstructionMetadata& MetadataProvider::GetConditionMetadata( + const gd::Platform& platform, gd::String conditionType) { + const auto* entry = + platform.GetMetadataIndex().GetConditionMetadata(conditionType); + return entry != nullptr ? *entry->metadata : badInstructionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndObjectExpressionMetadata( + const gd::Platform& platform, gd::String objectType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetObjectExpressionMetadata( + objectType, exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& MetadataProvider::GetObjectExpressionMetadata( + const gd::Platform& platform, gd::String objectType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetObjectExpressionMetadata( + objectType, exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndBehaviorExpressionMetadata( + const gd::Platform& platform, gd::String autoType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetBehaviorExpressionMetadata( + autoType, exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& MetadataProvider::GetBehaviorExpressionMetadata( + const gd::Platform& platform, gd::String autoType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetBehaviorExpressionMetadata( + autoType, exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndExpressionMetadata(const gd::Platform& platform, + gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetExpressionMetadata(exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& MetadataProvider::GetExpressionMetadata( + const gd::Platform& platform, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetExpressionMetadata(exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndObjectStrExpressionMetadata( + const gd::Platform& platform, gd::String objectType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetObjectStrExpressionMetadata( + objectType, exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& MetadataProvider::GetObjectStrExpressionMetadata( + const gd::Platform& platform, gd::String objectType, gd::String exprType) { + const auto* entry = platform.GetMetadataIndex().GetObjectStrExpressionMetadata( + objectType, exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndBehaviorStrExpressionMetadata( + const gd::Platform& platform, gd::String autoType, gd::String exprType) { + const auto* entry = + platform.GetMetadataIndex().GetBehaviorStrExpressionMetadata(autoType, + exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& +MetadataProvider::GetBehaviorStrExpressionMetadata(const gd::Platform& platform, + gd::String autoType, + gd::String exprType) { + const auto* entry = + platform.GetMetadataIndex().GetBehaviorStrExpressionMetadata(autoType, + exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +ExtensionAndMetadata +MetadataProvider::GetExtensionAndStrExpressionMetadata( + const gd::Platform& platform, gd::String exprType) { + const auto* entry = + platform.GetMetadataIndex().GetStrExpressionMetadata(exprType); + if (entry == nullptr) + return ExtensionAndMetadata(badExtension, + badExpressionMetadata); + return ExtensionAndMetadata(*entry->extension, + *entry->metadata); +} + +const gd::ExpressionMetadata& MetadataProvider::GetStrExpressionMetadata( + const gd::Platform& platform, gd::String exprType) { + const auto* entry = + platform.GetMetadataIndex().GetStrExpressionMetadata(exprType); + return entry != nullptr ? *entry->metadata : badExpressionMetadata; +} + +const gd::ExpressionMetadata& MetadataProvider::GetAnyExpressionMetadata( + const gd::Platform& platform, gd::String exprType) { + const auto& numberExpressionMetadata = + GetExpressionMetadata(platform, exprType); + if (&numberExpressionMetadata != &badExpressionMetadata) { + return numberExpressionMetadata; + } + const auto& stringExpressionMetadata = + GetStrExpressionMetadata(platform, exprType); + if (&stringExpressionMetadata != &badExpressionMetadata) { + return stringExpressionMetadata; + } + return badExpressionMetadata; +} + +const gd::ExpressionMetadata& MetadataProvider::GetObjectAnyExpressionMetadata( + const gd::Platform& platform, gd::String objectType, gd::String exprType) { + const auto& numberExpressionMetadata = + GetObjectExpressionMetadata(platform, objectType, exprType); + if (&numberExpressionMetadata != &badExpressionMetadata) { + return numberExpressionMetadata; + } + const auto& stringExpressionMetadata = + GetObjectStrExpressionMetadata(platform, objectType, exprType); + if (&stringExpressionMetadata != &badExpressionMetadata) { + return stringExpressionMetadata; + } + return badExpressionMetadata; +} + +const gd::ExpressionMetadata& +MetadataProvider::GetBehaviorAnyExpressionMetadata(const gd::Platform& platform, + gd::String autoType, + gd::String exprType) { + const auto& numberExpressionMetadata = + GetBehaviorExpressionMetadata(platform, autoType, exprType); + if (&numberExpressionMetadata != &badExpressionMetadata) { + return numberExpressionMetadata; + } + const auto& stringExpressionMetadata = + GetBehaviorStrExpressionMetadata(platform, autoType, exprType); + if (&stringExpressionMetadata != &badExpressionMetadata) { + return stringExpressionMetadata; + } + return badExpressionMetadata; +} + +const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata( + const gd::Platform& platform, + const gd::ObjectsContainersList &objectsContainersList, + FunctionCallNode& node) { + + if (!node.behaviorName.empty()) { + gd::String behaviorType = + objectsContainersList.GetTypeOfBehavior(node.behaviorName); + return MetadataProvider::GetBehaviorAnyExpressionMetadata( + platform, behaviorType, node.functionName); + } + else if (!node.objectName.empty()) { + gd::String objectType = + objectsContainersList.GetTypeOfObject(node.objectName); + return MetadataProvider::GetObjectAnyExpressionMetadata( + platform, objectType, node.functionName); + } + + return MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName); +} + +const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata( + const gd::Platform& platform, + const gd::ObjectsContainersList &objectsContainersList, + FunctionCallNode& functionCall, + ExpressionNode& parameter) { + int parameterIndex = -1; + for (int i = 0; i < functionCall.parameters.size(); i++) { + if (functionCall.parameters.at(i).get() == ¶meter) { + parameterIndex = i; + break; + } + } + if (parameterIndex < 0) { + return nullptr; + } + return MetadataProvider::GetFunctionCallParameterMetadata( + platform, + objectsContainersList, + functionCall, + parameterIndex); +} + +const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata( + const gd::Platform& platform, + const gd::ObjectsContainersList &objectsContainersList, + FunctionCallNode& functionCall, + int parameterIndex) { + // Search the parameter metadata index skipping invisible ones. + size_t visibleParameterIndex = 0; + size_t metadataParameterIndex = + ExpressionParser2::WrittenParametersFirstIndex( + functionCall.objectName, functionCall.behaviorName); + const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata( + platform, objectsContainersList, functionCall); + + if (IsBadExpressionMetadata(metadata)) { + return nullptr; + } + + // TODO use a badMetadata instead of a nullptr? + const gd::ParameterMetadata* parameterMetadata = nullptr; + while (metadataParameterIndex < + metadata.GetParameters().GetParametersCount()) { + if (!metadata.GetParameters().GetParameter(metadataParameterIndex) + .IsCodeOnly()) { + if (visibleParameterIndex == parameterIndex) { + parameterMetadata = + &metadata.GetParameters().GetParameter(metadataParameterIndex); + } + visibleParameterIndex++; + } + metadataParameterIndex++; + } + const int visibleParameterCount = visibleParameterIndex; + // It can be null if there are too many parameters in the expression, this text node is + // not actually linked to a parameter expected by the function call. + return parameterMetadata; +} + +MetadataProvider::~MetadataProvider() {} +MetadataProvider::MetadataProvider() {} + +} // namespace gd diff --git a/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.cpp b/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.cpp new file mode 100644 index 000000000000..483900da9ce1 --- /dev/null +++ b/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.cpp @@ -0,0 +1,212 @@ +/* + * GDevelop Core + * Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#include "GDCore/Extensions/Metadata/PlatformMetadataIndex.h" + +#include + +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/EffectMetadata.h" +#include "GDCore/Extensions/Metadata/ExpressionMetadata.h" +#include "GDCore/Extensions/Metadata/InstructionMetadata.h" +#include "GDCore/Extensions/Metadata/ObjectMetadata.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Extensions/PlatformExtension.h" + +namespace gd { + +namespace { + +// Index expressions (number or string) of an object/behavior type. `emplace` +// keeps the first inserted entry, so the first extension declaring a type wins, +// matching the previous linear-scan implementation. +void IndexExpressions( + std::unordered_map< + gd::String, + std::unordered_map>>& + expressionsByType, + const gd::String& type, + const gd::PlatformExtension* extension, + std::map& expressions) { + auto& byExprType = expressionsByType[type]; + for (auto& it : expressions) + byExprType.emplace(it.first, + MetadataEntry{extension, &it.second}); +} + +} // namespace + +PlatformMetadataIndex::PlatformMetadataIndex(const gd::Platform& platform) { + for (const auto& extensionPtr : platform.GetAllPlatformExtensions()) { + gd::PlatformExtension& extension = *extensionPtr; + const gd::PlatformExtension* ext = extensionPtr.get(); + + const std::vector objectTypes = + extension.GetExtensionObjectsTypes(); + const std::vector behaviorTypes = extension.GetBehaviorsTypes(); + + // Objects, behaviors and effects. + for (const auto& type : objectTypes) + objectMetadata.emplace( + type, + MetadataEntry{ext, &extension.GetObjectMetadata(type)}); + for (const auto& type : behaviorTypes) + behaviorMetadata.emplace( + type, MetadataEntry{ + ext, &extension.GetBehaviorMetadata(type)}); + for (const auto& type : extension.GetExtensionEffectTypes()) + effectMetadata.emplace( + type, + MetadataEntry{ext, &extension.GetEffectMetadata(type)}); + + // Actions and conditions: free, then per-object, then per-behavior. The + // instruction type is globally unique, so they share a single index. + for (auto& it : extension.GetAllActions()) + actions.emplace(it.first, + MetadataEntry{ext, &it.second}); + for (auto& it : extension.GetAllConditions()) + conditions.emplace(it.first, + MetadataEntry{ext, &it.second}); + for (const auto& objectType : objectTypes) { + for (auto& it : extension.GetAllActionsForObject(objectType)) + actions.emplace(it.first, + MetadataEntry{ext, &it.second}); + for (auto& it : extension.GetAllConditionsForObject(objectType)) + conditions.emplace(it.first, + MetadataEntry{ext, &it.second}); + } + for (const auto& behaviorType : behaviorTypes) { + for (auto& it : extension.GetAllActionsForBehavior(behaviorType)) + actions.emplace(it.first, + MetadataEntry{ext, &it.second}); + for (auto& it : extension.GetAllConditionsForBehavior(behaviorType)) + conditions.emplace(it.first, + MetadataEntry{ext, &it.second}); + } + + // Free expressions. + for (auto& it : extension.GetAllExpressions()) + expressions.emplace(it.first, + MetadataEntry{ext, &it.second}); + for (auto& it : extension.GetAllStrExpressions()) + strExpressions.emplace( + it.first, MetadataEntry{ext, &it.second}); + + // Object/behavior expressions, including the base ("") type. + IndexExpressions(objectExpressions, "", ext, + extension.GetAllExpressionsForObject("")); + IndexExpressions(objectStrExpressions, "", ext, + extension.GetAllStrExpressionsForObject("")); + for (const auto& objectType : objectTypes) { + IndexExpressions(objectExpressions, objectType, ext, + extension.GetAllExpressionsForObject(objectType)); + IndexExpressions(objectStrExpressions, objectType, ext, + extension.GetAllStrExpressionsForObject(objectType)); + } + + IndexExpressions(behaviorExpressions, "", ext, + extension.GetAllExpressionsForBehavior("")); + IndexExpressions(behaviorStrExpressions, "", ext, + extension.GetAllStrExpressionsForBehavior("")); + for (const auto& behaviorType : behaviorTypes) { + IndexExpressions(behaviorExpressions, behaviorType, ext, + extension.GetAllExpressionsForBehavior(behaviorType)); + IndexExpressions(behaviorStrExpressions, behaviorType, ext, + extension.GetAllStrExpressionsForBehavior(behaviorType)); + } + } +} + +namespace { +template +const MetadataEntry* FindInMap( + const std::unordered_map>& map, + const gd::String& type) { + auto it = map.find(type); + return it != map.end() ? &it->second : nullptr; +} +} // namespace + +const MetadataEntry* PlatformMetadataIndex::GetObjectMetadata( + const gd::String& type) const { + return FindInMap(objectMetadata, type); +} + +const MetadataEntry* +PlatformMetadataIndex::GetBehaviorMetadata(const gd::String& type) const { + return FindInMap(behaviorMetadata, type); +} + +const MetadataEntry* PlatformMetadataIndex::GetEffectMetadata( + const gd::String& type) const { + return FindInMap(effectMetadata, type); +} + +const MetadataEntry* +PlatformMetadataIndex::GetActionMetadata(const gd::String& type) const { + return FindInMap(actions, type); +} + +const MetadataEntry* +PlatformMetadataIndex::GetConditionMetadata(const gd::String& type) const { + return FindInMap(conditions, type); +} + +const MetadataEntry* +PlatformMetadataIndex::GetExpressionMetadata(const gd::String& type) const { + return FindInMap(expressions, type); +} + +const MetadataEntry* +PlatformMetadataIndex::GetStrExpressionMetadata(const gd::String& type) const { + return FindInMap(strExpressions, type); +} + +const MetadataEntry* +PlatformMetadataIndex::FindInExpressionsByType( + const ExpressionsByType& expressionsByType, + const gd::String& type, + const gd::String& exprType) { + auto outer = expressionsByType.find(type); + if (outer != expressionsByType.end()) { + auto inner = outer->second.find(exprType); + if (inner != outer->second.end()) return &inner->second; + } + + // Fall back to the base ("") object/behavior type. + auto base = expressionsByType.find(""); + if (base != expressionsByType.end()) { + auto inner = base->second.find(exprType); + if (inner != base->second.end()) return &inner->second; + } + + return nullptr; +} + +const MetadataEntry* +PlatformMetadataIndex::GetObjectExpressionMetadata( + const gd::String& objectType, const gd::String& exprType) const { + return FindInExpressionsByType(objectExpressions, objectType, exprType); +} + +const MetadataEntry* +PlatformMetadataIndex::GetObjectStrExpressionMetadata( + const gd::String& objectType, const gd::String& exprType) const { + return FindInExpressionsByType(objectStrExpressions, objectType, exprType); +} + +const MetadataEntry* +PlatformMetadataIndex::GetBehaviorExpressionMetadata( + const gd::String& behaviorType, const gd::String& exprType) const { + return FindInExpressionsByType(behaviorExpressions, behaviorType, exprType); +} + +const MetadataEntry* +PlatformMetadataIndex::GetBehaviorStrExpressionMetadata( + const gd::String& behaviorType, const gd::String& exprType) const { + return FindInExpressionsByType(behaviorStrExpressions, behaviorType, exprType); +} + +} // namespace gd diff --git a/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.h b/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.h new file mode 100644 index 000000000000..d23f024919e1 --- /dev/null +++ b/Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.h @@ -0,0 +1,119 @@ +/* + * GDevelop Core + * Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +#pragma once + +#include + +#include "GDCore/String.h" + +namespace gd { +class Platform; +class PlatformExtension; +class ObjectMetadata; +class BehaviorMetadata; +class EffectMetadata; +class InstructionMetadata; +class ExpressionMetadata; +} // namespace gd + +namespace gd { + +/** + * \brief A resolved metadata entry: the metadata and the extension declaring it. + * + * Both pointers are owned by the gd::PlatformExtension they come from. They stay + * valid as long as the index does: the index is rebuilt from scratch whenever + * the platform extensions change (see gd::Platform::AddExtension / + * RemoveExtension), so it never holds pointers to freed metadata. + */ +template +struct MetadataEntry { + const gd::PlatformExtension* extension; + const T* metadata; + + MetadataEntry() : extension(nullptr), metadata(nullptr) {} + MetadataEntry(const gd::PlatformExtension* extension_, const T* metadata_) + : extension(extension_), metadata(metadata_) {} +}; + +/** + * \brief Hash-map index of all the metadata declared by a platform's + * extensions, so that gd::MetadataProvider lookups are O(1) instead of a linear + * scan over every extension (and, for instructions/expressions, over every + * object and behavior type of every extension). + * + * It is built lazily and owned by gd::Platform, which discards it whenever its + * extensions change. The semantics of every lookup match the previous + * linear-scan implementation exactly: the first extension (in load order) + * declaring a type wins, and object/behavior expressions resolve the specific + * object/behavior type before falling back to the base ("") type. + * + * \ingroup PlatformDefinition + */ +class GD_CORE_API PlatformMetadataIndex { + public: + /** + * \brief Build the index from all the extensions of the given platform. + */ + explicit PlatformMetadataIndex(const gd::Platform& platform); + + const MetadataEntry* GetObjectMetadata( + const gd::String& type) const; + const MetadataEntry* GetBehaviorMetadata( + const gd::String& type) const; + const MetadataEntry* GetEffectMetadata( + const gd::String& type) const; + const MetadataEntry* GetActionMetadata( + const gd::String& type) const; + const MetadataEntry* GetConditionMetadata( + const gd::String& type) const; + const MetadataEntry* GetExpressionMetadata( + const gd::String& type) const; + const MetadataEntry* GetStrExpressionMetadata( + const gd::String& type) const; + + /** + * Object/behavior expressions resolve the specific type first, then fall back + * to the base ("") type, matching the previous implementation. + */ + const MetadataEntry* GetObjectExpressionMetadata( + const gd::String& objectType, const gd::String& exprType) const; + const MetadataEntry* GetObjectStrExpressionMetadata( + const gd::String& objectType, const gd::String& exprType) const; + const MetadataEntry* GetBehaviorExpressionMetadata( + const gd::String& behaviorType, const gd::String& exprType) const; + const MetadataEntry* GetBehaviorStrExpressionMetadata( + const gd::String& behaviorType, const gd::String& exprType) const; + + private: + // Single-key indexes (the type is globally unique). + std::unordered_map> objectMetadata; + std::unordered_map> + behaviorMetadata; + std::unordered_map> effectMetadata; + std::unordered_map> actions; + std::unordered_map> conditions; + std::unordered_map> expressions; + std::unordered_map> + strExpressions; + + // Composite-key indexes: object/behavior type -> (expression type -> entry). + // The base ("") type is stored under the "" outer key. + using ExpressionsByType = std::unordered_map< + gd::String, + std::unordered_map>>; + ExpressionsByType objectExpressions; + ExpressionsByType objectStrExpressions; + ExpressionsByType behaviorExpressions; + ExpressionsByType behaviorStrExpressions; + + static const MetadataEntry* FindInExpressionsByType( + const ExpressionsByType& expressionsByType, + const gd::String& type, + const gd::String& exprType); +}; + +} // namespace gd diff --git a/Core/GDCore/Extensions/Platform.cpp b/Core/GDCore/Extensions/Platform.cpp index 97ebf6330be0..ae95649c2491 100644 --- a/Core/GDCore/Extensions/Platform.cpp +++ b/Core/GDCore/Extensions/Platform.cpp @@ -5,6 +5,7 @@ */ #include "Platform.h" +#include "GDCore/Extensions/Metadata/PlatformMetadataIndex.h" #include "GDCore/Extensions/PlatformExtension.h" #include "GDCore/Project/Object.h" #include "GDCore/Project/ObjectConfiguration.h" @@ -24,9 +25,19 @@ Platform::Platform() : enableExtensionLoadingLogs(false) {} Platform::~Platform() {} +const gd::PlatformMetadataIndex& Platform::GetMetadataIndex() const { + if (!metadataIndex) + metadataIndex.reset(new gd::PlatformMetadataIndex(*this)); + return *metadataIndex; +} + bool Platform::AddExtension(std::shared_ptr extension) { if (!extension) return false; + // The set of extensions is changing: discard the metadata index so it is + // rebuilt on the next lookup. + metadataIndex.reset(); + if (enableExtensionLoadingLogs) std::cout << "Loading " << extension->GetName() << "..."; if (IsExtensionLoaded(extension->GetName())) { @@ -57,6 +68,10 @@ bool Platform::AddExtension(std::shared_ptr extension) { } void Platform::RemoveExtension(const gd::String& name) { + // The set of extensions is changing: discard the metadata index so it is + // rebuilt on the next lookup. + metadataIndex.reset(); + // Unload all creation/destruction functions for objects provided by the // extension for (std::size_t i = 0; i < extensionsLoaded.size(); ++i) { diff --git a/Core/GDCore/Extensions/Platform.h b/Core/GDCore/Extensions/Platform.h index 9e088a2d5459..97b7f0b1e2ee 100644 --- a/Core/GDCore/Extensions/Platform.h +++ b/Core/GDCore/Extensions/Platform.h @@ -25,6 +25,7 @@ class BehaviorsSharedData; class PlatformExtension; class LayoutEditorCanvas; class ProjectExporter; +class PlatformMetadataIndex; } // namespace gd typedef std::function()> @@ -116,6 +117,15 @@ class GD_CORE_API Platform { return extensionsLoaded; }; + /** + * \brief Get the (lazily built) index of all the metadata declared by the + * platform's extensions, allowing constant-time lookups by gd::MetadataProvider. + * + * \note The index is discarded whenever extensions change (see AddExtension + * and RemoveExtension), so it never returns stale metadata. + */ + const gd::PlatformMetadataIndex& GetMetadataIndex() const; + /** * \brief Remove an extension from the platform. * @@ -174,6 +184,12 @@ class GD_CORE_API Platform { instructionOrExpressionGroupMetadata; static InstructionOrExpressionGroupMetadata badInstructionOrExpressionGroupMetadata; bool enableExtensionLoadingLogs; + + /// Lazily built metadata index, discarded whenever extensions change. + /// A shared_ptr (rather than unique_ptr) keeps gd::Platform copyable; copies + /// share the index until either one changes its extensions, which resets only + /// that instance's pointer and rebuilds it independently. + mutable std::shared_ptr metadataIndex; }; } // namespace gd diff --git a/Core/GDCore/IDE/Project/ArbitraryResourceWorker.cpp b/Core/GDCore/IDE/Project/ArbitraryResourceWorker.cpp index 712cca772fd2..2dd5d7a1dc98 100644 --- a/Core/GDCore/IDE/Project/ArbitraryResourceWorker.cpp +++ b/Core/GDCore/IDE/Project/ArbitraryResourceWorker.cpp @@ -129,7 +129,6 @@ void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) { child.second->GetValue().GetString(); if (resourcesManager->HasResource(targetResourceName)) { - std::cout << targetResourceName << std::endl; gd::Resource& targetResource = resourcesManager->GetResource(targetResourceName); @@ -234,6 +233,14 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi const gd::ParameterMetadata ¶meterMetadata, const gd::Expression ¶meterExpression, size_t parameterIndex, const gd::String &lastObjectName, size_t lastObjectIndex) { + // Only resource parameters can refer to a resource. Checking this + // before looking the value up in the resources containers avoids an + // expensive (linear) resources lookup for the many non-resource + // parameters (numbers, strings, objects, expressions...). + if (!parameterMetadata.GetValueTypeMetadata().IsResource()) { + return; + } + const String& parameterValue = parameterExpression.GetPlainString(); const auto resourceSourceType = resourcesContainersList.GetResourcesContainerSourceType( diff --git a/Core/tests/PlatformMetadataIndex.cpp b/Core/tests/PlatformMetadataIndex.cpp new file mode 100644 index 000000000000..cb27adc09978 --- /dev/null +++ b/Core/tests/PlatformMetadataIndex.cpp @@ -0,0 +1,114 @@ +/* + * GDevelop Core + * Copyright 2008-2026 Florian Rival (Florian.Rival@gmail.com). All rights + * reserved. This project is released under the MIT License. + */ +/** + * @file Tests for gd::MetadataProvider lookups, which are backed by + * gd::PlatformMetadataIndex (constant-time index of a platform's metadata). + */ +#include "DummyPlatform.h" +#include "GDCore/Extensions/Metadata/BehaviorMetadata.h" +#include "GDCore/Extensions/Metadata/EffectMetadata.h" +#include "GDCore/Extensions/Metadata/ExpressionMetadata.h" +#include "GDCore/Extensions/Metadata/InstructionMetadata.h" +#include "GDCore/Extensions/Metadata/MetadataProvider.h" +#include "GDCore/Extensions/Metadata/ObjectMetadata.h" +#include "GDCore/Extensions/Platform.h" +#include "GDCore/Extensions/PlatformExtension.h" +#include "GDCore/Project/Project.h" +#include "catch.hpp" + +using namespace gd; + +TEST_CASE("PlatformMetadataIndex (via MetadataProvider)", "[common]") { + gd::Project project; + gd::Platform platform; + SetupProjectWithDummyPlatform(project, platform); + + SECTION("It resolves every kind of metadata declared by an extension") { + REQUIRE_FALSE(MetadataProvider::IsBadObjectMetadata( + MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite"))); + REQUIRE_FALSE(MetadataProvider::IsBadBehaviorMetadata( + MetadataProvider::GetBehaviorMetadata(platform, + "MyExtension::MyBehavior"))); + REQUIRE_FALSE(MetadataProvider::IsBadInstructionMetadata( + MetadataProvider::GetActionMetadata(platform, "MyExtension::DoSomething"))); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetExpressionMetadata(platform, "MyExtension::GetNumber"))); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetStrExpressionMetadata(platform, "MyExtension::ToString"))); + } + + SECTION("Behavior actions are indexed alongside free/object actions") { + REQUIRE_FALSE(MetadataProvider::IsBadInstructionMetadata( + MetadataProvider::GetActionMetadata( + platform, "MyExtension::BehaviorDoSomething"))); + } + + SECTION("It resolves object/behavior expressions on their own type") { + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetObjectExpressionMetadata( + platform, "MyExtension::Sprite", "GetObjectNumber"))); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetObjectStrExpressionMetadata( + platform, "MyExtension::Sprite", "GetObjectStringWith1Param"))); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetBehaviorExpressionMetadata( + platform, "MyExtension::MyBehavior", "GetBehaviorNumberWith1Param"))); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetBehaviorStrExpressionMetadata( + platform, "MyExtension::MyBehavior", "GetBehaviorStringWith1Param"))); + } + + SECTION("Object expressions fall back to the base object type") { + // "GetFromBaseExpression" is declared on the base object (""), not on + // Sprite, so it must be resolved through the base-type fallback. + const auto& fromBase = MetadataProvider::GetObjectExpressionMetadata( + platform, "MyExtension::Sprite", "GetFromBaseExpression"); + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata(fromBase)); + + // It must also be found for an object type that is not declared by any + // extension (still resolved via the base object). + REQUIRE_FALSE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetObjectExpressionMetadata( + platform, "UnknownObjectType", "GetFromBaseExpression"))); + } + + SECTION("Unknown types resolve to the bad metadata") { + REQUIRE(MetadataProvider::IsBadObjectMetadata( + MetadataProvider::GetObjectMetadata(platform, "MyExtension::DoesNotExist"))); + REQUIRE(MetadataProvider::IsBadBehaviorMetadata( + MetadataProvider::GetBehaviorMetadata(platform, "Does::NotExist"))); + REQUIRE(MetadataProvider::IsBadInstructionMetadata( + MetadataProvider::GetActionMetadata(platform, "Does::NotExist"))); + REQUIRE(MetadataProvider::IsBadInstructionMetadata( + MetadataProvider::GetConditionMetadata(platform, "Does::NotExist"))); + REQUIRE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetExpressionMetadata(platform, "Does::NotExist"))); + REQUIRE(MetadataProvider::IsBadExpressionMetadata( + MetadataProvider::GetObjectExpressionMetadata( + platform, "MyExtension::Sprite", "DoesNotExist"))); + } + + SECTION("It returns the extension that declares the metadata") { + auto extensionAndMetadata = + MetadataProvider::GetExtensionAndObjectMetadata(platform, + "MyExtension::Sprite"); + REQUIRE(extensionAndMetadata.GetExtension().GetName() == "MyExtension"); + + auto badExtensionAndMetadata = + MetadataProvider::GetExtensionAndObjectMetadata(platform, + "MyExtension::DoesNotExist"); + REQUIRE(badExtensionAndMetadata.GetExtension().GetName().empty()); + } + + SECTION("The index is rebuilt after the platform's extensions change") { + REQUIRE_FALSE(MetadataProvider::IsBadObjectMetadata( + MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite"))); + + platform.RemoveExtension("MyExtension"); + REQUIRE(MetadataProvider::IsBadObjectMetadata( + MetadataProvider::GetObjectMetadata(platform, "MyExtension::Sprite"))); + } +} diff --git a/GDevelop.js/__tests__/MetadataProviderIndex.js b/GDevelop.js/__tests__/MetadataProviderIndex.js new file mode 100644 index 000000000000..46f0bdba0a9a --- /dev/null +++ b/GDevelop.js/__tests__/MetadataProviderIndex.js @@ -0,0 +1,146 @@ +const initializeGDevelopJs = require('../../Binaries/embuild/GDevelop.js/libGD.js'); + +// Checks that gd::MetadataProvider lookups (backed by the platform's +// gd::PlatformMetadataIndex) stay correct when extensions are added, replaced +// and removed at runtime - i.e. that the index is properly invalidated - and +// that every kind of metadata is indexed and resolved. +describe('MetadataProvider index', () => { + let gd = null; + beforeAll(async () => { + gd = await initializeGDevelopJs(); + }); + + const extensionName = 'MetadataIndexTestExtension'; + const prefix = extensionName + '::'; + const objectType = prefix + 'TestObject'; + const behaviorType = prefix + 'TestBehavior'; + const actionType = prefix + 'TestAction'; + const conditionType = prefix + 'TestCondition'; + const expressionType = prefix + 'TestExpression'; + const strExpressionType = prefix + 'TestStrExpression'; + // Object/behavior expression names are not namespaced (they are scoped to the + // object/behavior type they belong to). + const objectExpressionName = 'TestObjectExpression'; + const objectStrExpressionName = 'TestObjectStrExpression'; + const behaviorExpressionName = 'TestBehaviorExpression'; + const behaviorStrExpressionName = 'TestBehaviorStrExpression'; + + const makeExtension = (objectFullName) => { + const extension = new gd.PlatformExtension(); + extension.setExtensionInformation( + extensionName, + 'Metadata index test extension', + 'Description', + 'Author', + 'MIT' + ); + + const objectConfiguration = new gd.ObjectJsImplementation(); + const objectMetadata = extension.addObject( + 'TestObject', + objectFullName, + 'A test object', + '', + objectConfiguration + ); + objectMetadata.addExpression(objectExpressionName, 'Obj expr', 'd', 'g', ''); + objectMetadata.addStrExpression(objectStrExpressionName, 'Obj str', 'd', 'g', ''); + + const behaviorInstance = new gd.BehaviorJsImplementation(); + behaviorInstance.initializeContent = function (behaviorContent) {}; + const behaviorMetadata = extension.addBehavior( + 'TestBehavior', + 'Test behavior', + 'TestBehavior', + 'A test behavior', + '', + '', + 'TestBehavior', + behaviorInstance, + new gd.BehaviorsSharedData() + ); + behaviorMetadata.addExpression(behaviorExpressionName, 'Beh expr', 'd', 'g', ''); + behaviorMetadata.addStrExpression(behaviorStrExpressionName, 'Beh str', 'd', 'g', ''); + + extension.addAction('TestAction', 'Test action', 'Does', 'Does', '', '', ''); + extension.addCondition('TestCondition', 'Test cond', 'Is', 'Is', '', '', ''); + extension.addExpression('TestExpression', 'Test expr', 'd', 'g', ''); + extension.addStrExpression('TestStrExpression', 'Test str', 'd', 'g', ''); + + return extension; + }; + + // Asserts every kind of metadata declared by makeExtension is found (when + // expectFound) or resolves to the "bad" metadata (when not). + const expectAllResolved = (platform, expectFound) => { + const P = gd.MetadataProvider; + const cases = [ + P.isBadObjectMetadata(P.getObjectMetadata(platform, objectType)), + P.isBadBehaviorMetadata(P.getBehaviorMetadata(platform, behaviorType)), + P.isBadInstructionMetadata(P.getActionMetadata(platform, actionType)), + P.isBadInstructionMetadata(P.getConditionMetadata(platform, conditionType)), + P.isBadExpressionMetadata(P.getExpressionMetadata(platform, expressionType)), + P.isBadExpressionMetadata(P.getStrExpressionMetadata(platform, strExpressionType)), + P.isBadExpressionMetadata( + P.getObjectExpressionMetadata(platform, objectType, objectExpressionName) + ), + P.isBadExpressionMetadata( + P.getObjectStrExpressionMetadata(platform, objectType, objectStrExpressionName) + ), + P.isBadExpressionMetadata( + P.getBehaviorExpressionMetadata(platform, behaviorType, behaviorExpressionName) + ), + P.isBadExpressionMetadata( + P.getBehaviorStrExpressionMetadata(platform, behaviorType, behaviorStrExpressionName) + ), + ]; + // When found, none should be "bad"; when not, all should be "bad". + for (const isBad of cases) expect(isBad).toBe(!expectFound); + }; + + afterEach(() => { + if (gd.JsPlatform.get().isExtensionLoaded(extensionName)) + gd.JsPlatform.get().removeExtension(extensionName); + }); + + it('resolves every kind of metadata after the extension is added, and none before/after removal', () => { + const platform = gd.JsPlatform.get(); + + // Build the index once first so we test invalidation, not lazy first build. + gd.MetadataProvider.getObjectMetadata(platform, 'Sprite'); + + expectAllResolved(platform, false); + + const extension = makeExtension('First name'); + platform.addNewExtension(extension); + extension.delete(); + + expectAllResolved(platform, true); + + platform.removeExtension(extensionName); + + expectAllResolved(platform, false); + }); + + it('returns updated metadata after the extension is replaced (not stale)', () => { + const platform = gd.JsPlatform.get(); + + const firstExtension = makeExtension('First name'); + platform.addNewExtension(firstExtension); + firstExtension.delete(); + + expect( + gd.MetadataProvider.getObjectMetadata(platform, objectType).getFullName() + ).toBe('First name'); + + // Replacing goes through RemoveExtension + AddExtension and must discard the + // cached (now dangling) metadata. + const secondExtension = makeExtension('Second name'); + platform.addNewExtension(secondExtension); + secondExtension.delete(); + + expect( + gd.MetadataProvider.getObjectMetadata(platform, objectType).getFullName() + ).toBe('Second name'); + }); +}); diff --git a/GDevelop.js/scripts/bench-events-code-generation.js b/GDevelop.js/scripts/bench-events-code-generation.js new file mode 100644 index 000000000000..73db59927434 --- /dev/null +++ b/GDevelop.js/scripts/bench-events-code-generation.js @@ -0,0 +1,122 @@ +// Benchmark events code generation on synthetic scenes (the work done by +// "Events code export" on every preview/export), scaling objects, events and +// object-group usage independently. +// +// This isolates how code generation scales, which is useful to catch +// regressions in the per-instruction work. In particular, object/behavior type +// resolution (gd::ObjectsContainersList::GetTypeOfObject) is a linear scan over +// the objects, so referencing a large object GROUP in many events is the +// pathological case (each reference expands to every member, each resolved with +// an O(objects) scan). +// +// To profile a REAL project's per-scene code generation instead, see +// scripts/profile-events-code-generation.js. +// +// Requires GDevelop.js to be built (Binaries/embuild/GDevelop.js/libGD.js). +// +// Usage: +// node scripts/bench-events-code-generation.js + +const path = require('path'); +const init = require( + path.join(__dirname, '../../Binaries/embuild/GDevelop.js/libGD.js') +); + +// Build a single scene with `numObjects` objects and `numEvents` events. Each +// event has a position condition targeting either a single object or a group +// containing all objects (`useGroup`), and a variable action whose expression +// is `exprLen` operands long. +function buildLayout(gd, project, name, numObjects, numEvents, useGroup, exprLen) { + const layout = project.insertNewLayout(name, project.getLayoutsCount()); + for (let o = 0; o < numObjects; o++) { + layout.getObjects().insertNewObject(project, 'Sprite', 'Obj' + o, o); + } + if (useGroup) { + const group = layout.getObjects().getObjectGroups().insertNew('AllObjects', 0); + for (let o = 0; o < numObjects; o++) group.addObject('Obj' + o); + } + const target = useGroup ? 'AllObjects' : 'Obj0'; + + const events = layout.getEvents(); + for (let e = 0; e < numEvents; e++) { + const event = gd.asStandardEvent( + events.insertNewEvent(project, 'BuiltinCommonInstructions::Standard', e) + ); + + const condition = new gd.Instruction(); + condition.setType('PosX'); + condition.setParametersCount(3); + condition.setParameter(0, target); + condition.setParameter(1, '<'); + condition.setParameter(2, '100'); + event.getConditions().insert(condition, 0); + condition.delete(); + + const expression = + exprLen > 0 + ? Array.from({ length: exprLen }, (_, i) => (i % 9) + 1).join('+') + : '1'; + const action = new gd.Instruction(); + action.setType('BuiltinCommonInstructions::SetNumberVariable'); + action.setParametersCount(3); + action.setParameter(0, 'MyVar'); + action.setParameter(1, '='); + action.setParameter(2, expression); + event.getActions().insert(action, 0); + action.delete(); + } + return layout; +} + +function timeCodegen(gd, project, layout, runs) { + let best = Infinity; + for (let i = 0; i < runs; i++) { + const includeFiles = new gd.SetString(); + const generator = new gd.LayoutCodeGenerator(project); + const report = new gd.DiagnosticReport(); + const t = process.hrtime.bigint(); + generator.generateLayoutCompleteCode(layout, includeFiles, report, true); + best = Math.min(best, Number(process.hrtime.bigint() - t) / 1e6); + generator.delete(); + includeFiles.delete(); + report.delete(); + } + return best; +} + +(async () => { + const gd = await init({ print: () => {}, printErr: () => {} }); + + const scenarios = [ + // Vary object count, fixed events (single-object reference -> flat). + { label: 'objects', objects: 500, events: 2000, group: false, exprLen: 1 }, + { label: 'objects', objects: 2000, events: 2000, group: false, exprLen: 1 }, + // Vary event count (linear). + { label: 'events', objects: 200, events: 2000, group: false, exprLen: 1 }, + { label: 'events', objects: 200, events: 8000, group: false, exprLen: 1 }, + // Object groups: each reference expands to every member (pathological). + { label: 'group', objects: 100, events: 200, group: true, exprLen: 1 }, + { label: 'group', objects: 200, events: 200, group: true, exprLen: 1 }, + { label: 'group', objects: 400, events: 200, group: true, exprLen: 1 }, + // Large expressions. + { label: 'big expr', objects: 50, events: 200, group: false, exprLen: 4000 }, + ]; + + console.log('scenario | objects | events | group | exprLen | codegen ms'); + let i = 0; + for (const s of scenarios) { + const project = gd.ProjectHelper.createNewGDJSProject(); + const layout = buildLayout( + gd, project, 'Scene' + i++, s.objects, s.events, s.group, s.exprLen + ); + const ms = timeCodegen(gd, project, layout, 2); + console.log( + `${s.label.padEnd(8)} | ${String(s.objects).padStart(7)} | ${String( + s.events + ).padStart(6)} | ${String(s.group).padStart(5)} | ${String( + s.exprLen + ).padStart(7)} | ${ms.toFixed(0).padStart(8)}` + ); + project.delete(); + } +})(); diff --git a/GDevelop.js/scripts/bench-resource-finding.js b/GDevelop.js/scripts/bench-resource-finding.js new file mode 100644 index 000000000000..e2e2c7d5f782 --- /dev/null +++ b/GDevelop.js/scripts/bench-resource-finding.js @@ -0,0 +1,105 @@ +// Benchmark resource finding on a synthetic project (the work done by +// "Resource export" and "Project data export" on every preview/export). +// +// It runs `gd::ResourceExposer::ExposeWholeProjectResources` (which walks every +// scene's objects and events looking for used resources) and times it while +// scaling the number of resources. This is handy to catch regressions where a +// per-parameter/per-object resource lookup becomes expensive: resource +// containers look resources up with a linear scan, so an O(1)-looking call in a +// hot loop is actually O(resources). +// +// To profile a REAL project's per-scene code generation instead, see +// scripts/profile-events-code-generation.js. +// +// Requires GDevelop.js to be built (Binaries/embuild/GDevelop.js/libGD.js). +// +// Usage: +// node scripts/bench-resource-finding.js [scenes] [objects] [events] +// [scenes] Scenes in the project. Default: 10. +// [objects] Objects per scene. Default: 50. +// [events] Events per scene (each with non-resource parameters, the +// worst case for the resource lookup). Default: 600. + +const path = require('path'); +const init = require( + path.join(__dirname, '../../Binaries/embuild/GDevelop.js/libGD.js') +); + +const numScenes = Math.max(1, parseInt(process.argv[2], 10) || 10); +const numObjects = Math.max(1, parseInt(process.argv[3], 10) || 50); +const numEvents = Math.max(1, parseInt(process.argv[4], 10) || 600); + +// Build a project with `numResources` image resources and, in every scene, +// `numObjects` objects and `numEvents` events made of non-resource instructions +// (a variable action and an object condition). Non-resource parameters are the +// worst case: each one is looked up against every resource and not found. +function buildProject(gd, numResources) { + const project = gd.ProjectHelper.createNewGDJSProject(); + const resourcesManager = project.getResourcesManager(); + for (let r = 0; r < numResources; r++) { + const resource = new gd.ImageResource(); + resource.setName('res' + r + '.png'); + resourcesManager.addResource(resource); + resource.delete(); + } + + for (let s = 0; s < numScenes; s++) { + const layout = project.insertNewLayout('Scene' + s, project.getLayoutsCount()); + for (let o = 0; o < numObjects; o++) { + layout.getObjects().insertNewObject(project, 'Sprite', 'Obj' + o, o); + } + const events = layout.getEvents(); + for (let e = 0; e < numEvents; e++) { + const event = gd.asStandardEvent( + events.insertNewEvent(project, 'BuiltinCommonInstructions::Standard', e) + ); + + const action = new gd.Instruction(); + action.setType('BuiltinCommonInstructions::SetNumberVariable'); + action.setParametersCount(3); + action.setParameter(0, 'MyVar'); + action.setParameter(1, '='); + action.setParameter(2, String(e % 100)); + event.getActions().insert(action, 0); + action.delete(); + + const condition = new gd.Instruction(); + condition.setType('PosX'); + condition.setParametersCount(3); + condition.setParameter(0, 'Obj' + (e % numObjects)); + condition.setParameter(1, '<'); + condition.setParameter(2, '100'); + event.getConditions().insert(condition, 0); + condition.delete(); + } + } + return project; +} + +function timeResourceFinding(gd, project, runs) { + let best = Infinity; + for (let i = 0; i < runs; i++) { + const worker = new gd.ResourcesInUseHelper(project.getResourcesManager()); + const t = process.hrtime.bigint(); + gd.ResourceExposer.exposeWholeProjectResources(project, worker); + best = Math.min(best, Number(process.hrtime.bigint() - t) / 1e6); + worker.delete(); + } + return best; +} + +(async () => { + const gd = await init({ print: () => {}, printErr: () => {} }); + + console.log( + `Scenes: ${numScenes}, objects/scene: ${numObjects}, events/scene: ${numEvents}` + ); + console.log(''); + console.log('resources | resource finding (best of 3)'); + for (const numResources of [200, 400, 800, 1600, 3200]) { + const project = buildProject(gd, numResources); + const ms = timeResourceFinding(gd, project, 3); + console.log(`${String(numResources).padStart(9)} | ${ms.toFixed(0).padStart(6)} ms`); + project.delete(); + } +})();