Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
842 changes: 365 additions & 477 deletions Core/GDCore/Extensions/Metadata/MetadataProvider.cpp

Large diffs are not rendered by default.

212 changes: 212 additions & 0 deletions Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.cpp
Original file line number Diff line number Diff line change
@@ -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 <map>

#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<gd::String, MetadataEntry<ExpressionMetadata>>>&
expressionsByType,
const gd::String& type,
const gd::PlatformExtension* extension,
std::map<gd::String, gd::ExpressionMetadata>& expressions) {
auto& byExprType = expressionsByType[type];
for (auto& it : expressions)
byExprType.emplace(it.first,
MetadataEntry<ExpressionMetadata>{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<gd::String> objectTypes =
extension.GetExtensionObjectsTypes();
const std::vector<gd::String> behaviorTypes = extension.GetBehaviorsTypes();

// Objects, behaviors and effects.
for (const auto& type : objectTypes)
objectMetadata.emplace(
type,
MetadataEntry<ObjectMetadata>{ext, &extension.GetObjectMetadata(type)});
for (const auto& type : behaviorTypes)
behaviorMetadata.emplace(
type, MetadataEntry<BehaviorMetadata>{
ext, &extension.GetBehaviorMetadata(type)});
for (const auto& type : extension.GetExtensionEffectTypes())
effectMetadata.emplace(
type,
MetadataEntry<EffectMetadata>{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<InstructionMetadata>{ext, &it.second});
for (auto& it : extension.GetAllConditions())
conditions.emplace(it.first,
MetadataEntry<InstructionMetadata>{ext, &it.second});
for (const auto& objectType : objectTypes) {
for (auto& it : extension.GetAllActionsForObject(objectType))
actions.emplace(it.first,
MetadataEntry<InstructionMetadata>{ext, &it.second});
for (auto& it : extension.GetAllConditionsForObject(objectType))
conditions.emplace(it.first,
MetadataEntry<InstructionMetadata>{ext, &it.second});
}
for (const auto& behaviorType : behaviorTypes) {
for (auto& it : extension.GetAllActionsForBehavior(behaviorType))
actions.emplace(it.first,
MetadataEntry<InstructionMetadata>{ext, &it.second});
for (auto& it : extension.GetAllConditionsForBehavior(behaviorType))
conditions.emplace(it.first,
MetadataEntry<InstructionMetadata>{ext, &it.second});
}

// Free expressions.
for (auto& it : extension.GetAllExpressions())
expressions.emplace(it.first,
MetadataEntry<ExpressionMetadata>{ext, &it.second});
for (auto& it : extension.GetAllStrExpressions())
strExpressions.emplace(
it.first, MetadataEntry<ExpressionMetadata>{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 <class T>
const MetadataEntry<T>* FindInMap(
const std::unordered_map<gd::String, MetadataEntry<T>>& map,
const gd::String& type) {
auto it = map.find(type);
return it != map.end() ? &it->second : nullptr;
}
} // namespace

const MetadataEntry<ObjectMetadata>* PlatformMetadataIndex::GetObjectMetadata(
const gd::String& type) const {
return FindInMap(objectMetadata, type);
}

const MetadataEntry<BehaviorMetadata>*
PlatformMetadataIndex::GetBehaviorMetadata(const gd::String& type) const {
return FindInMap(behaviorMetadata, type);
}

const MetadataEntry<EffectMetadata>* PlatformMetadataIndex::GetEffectMetadata(
const gd::String& type) const {
return FindInMap(effectMetadata, type);
}

const MetadataEntry<InstructionMetadata>*
PlatformMetadataIndex::GetActionMetadata(const gd::String& type) const {
return FindInMap(actions, type);
}

const MetadataEntry<InstructionMetadata>*
PlatformMetadataIndex::GetConditionMetadata(const gd::String& type) const {
return FindInMap(conditions, type);
}

const MetadataEntry<ExpressionMetadata>*
PlatformMetadataIndex::GetExpressionMetadata(const gd::String& type) const {
return FindInMap(expressions, type);
}

const MetadataEntry<ExpressionMetadata>*
PlatformMetadataIndex::GetStrExpressionMetadata(const gd::String& type) const {
return FindInMap(strExpressions, type);
}

const MetadataEntry<ExpressionMetadata>*
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<ExpressionMetadata>*
PlatformMetadataIndex::GetObjectExpressionMetadata(
const gd::String& objectType, const gd::String& exprType) const {
return FindInExpressionsByType(objectExpressions, objectType, exprType);
}

const MetadataEntry<ExpressionMetadata>*
PlatformMetadataIndex::GetObjectStrExpressionMetadata(
const gd::String& objectType, const gd::String& exprType) const {
return FindInExpressionsByType(objectStrExpressions, objectType, exprType);
}

const MetadataEntry<ExpressionMetadata>*
PlatformMetadataIndex::GetBehaviorExpressionMetadata(
const gd::String& behaviorType, const gd::String& exprType) const {
return FindInExpressionsByType(behaviorExpressions, behaviorType, exprType);
}

const MetadataEntry<ExpressionMetadata>*
PlatformMetadataIndex::GetBehaviorStrExpressionMetadata(
const gd::String& behaviorType, const gd::String& exprType) const {
return FindInExpressionsByType(behaviorStrExpressions, behaviorType, exprType);
}

} // namespace gd
119 changes: 119 additions & 0 deletions Core/GDCore/Extensions/Metadata/PlatformMetadataIndex.h
Original file line number Diff line number Diff line change
@@ -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 <unordered_map>

#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 <class T>
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<ObjectMetadata>* GetObjectMetadata(
const gd::String& type) const;
const MetadataEntry<BehaviorMetadata>* GetBehaviorMetadata(
const gd::String& type) const;
const MetadataEntry<EffectMetadata>* GetEffectMetadata(
const gd::String& type) const;
const MetadataEntry<InstructionMetadata>* GetActionMetadata(
const gd::String& type) const;
const MetadataEntry<InstructionMetadata>* GetConditionMetadata(
const gd::String& type) const;
const MetadataEntry<ExpressionMetadata>* GetExpressionMetadata(
const gd::String& type) const;
const MetadataEntry<ExpressionMetadata>* 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<ExpressionMetadata>* GetObjectExpressionMetadata(
const gd::String& objectType, const gd::String& exprType) const;
const MetadataEntry<ExpressionMetadata>* GetObjectStrExpressionMetadata(
const gd::String& objectType, const gd::String& exprType) const;
const MetadataEntry<ExpressionMetadata>* GetBehaviorExpressionMetadata(
const gd::String& behaviorType, const gd::String& exprType) const;
const MetadataEntry<ExpressionMetadata>* GetBehaviorStrExpressionMetadata(
const gd::String& behaviorType, const gd::String& exprType) const;

private:
// Single-key indexes (the type is globally unique).
std::unordered_map<gd::String, MetadataEntry<ObjectMetadata>> objectMetadata;
std::unordered_map<gd::String, MetadataEntry<BehaviorMetadata>>
behaviorMetadata;
std::unordered_map<gd::String, MetadataEntry<EffectMetadata>> effectMetadata;
std::unordered_map<gd::String, MetadataEntry<InstructionMetadata>> actions;
std::unordered_map<gd::String, MetadataEntry<InstructionMetadata>> conditions;
std::unordered_map<gd::String, MetadataEntry<ExpressionMetadata>> expressions;
std::unordered_map<gd::String, MetadataEntry<ExpressionMetadata>>
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<gd::String, MetadataEntry<ExpressionMetadata>>>;
ExpressionsByType objectExpressions;
ExpressionsByType objectStrExpressions;
ExpressionsByType behaviorExpressions;
ExpressionsByType behaviorStrExpressions;

static const MetadataEntry<ExpressionMetadata>* FindInExpressionsByType(
const ExpressionsByType& expressionsByType,
const gd::String& type,
const gd::String& exprType);
};

} // namespace gd
15 changes: 15 additions & 0 deletions Core/GDCore/Extensions/Platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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<gd::PlatformExtension> 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())) {
Expand Down Expand Up @@ -57,6 +68,10 @@ bool Platform::AddExtension(std::shared_ptr<gd::PlatformExtension> 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) {
Expand Down
Loading
Loading