Système de réflexion de GDevelop : mise en œuvre des informations de type à l'exécution et des appels dynamiques

Introduction

Dans le développement de jeux, le système de réflexion est une tcehnologie clé pour implémenter des comportements dynamiques, des extensions de plugins et la programmation visuelle. GDevelop, en tant que moteur de jeu open source et multiplateforme, fournit aux développeurs des capacités robustes d'informations de type à l'exécution (RTTI) et d'appels dynamiques grâce à une architecture méticuleusement conçue. Cet article examine en profondeur les principes d'implémentation, les composants centraux et les applications pratiques du système de réflexion de GDevelop.

Architecture du système de réflexion

Le système de réflexion de GDevelop adopte une architecture en couches, principalement composée de trois niveaux fondamentaux :

Classes de métadonnées fondamentales

GDevelop utilise une série de classes de métadonnées pour décrire les comportements et attributs des éléments du jeu :

Classe MetadonneesInstruction

MetadonneesInstruction est le noyau du système de réflexion, responsable de la description complète des instructions (actions et conditions) :

class MetadonneesInstruction : public gd::MetadonneesFonctionAbstrait {
public:
    // Constructeur
    MetadonneesInstruction(const gd::Chaine &espaceNomExtension,
                           const gd::Chaine &nom,
                           const gd::Chaine &nomComplet,
                           const gd::Chaine &description,
                           const gd::Chaine &phrase,
                           const gd::Chaine &groupe,
                           const gd::Chaine &icone,
                           const gd::Chaine &petiteIcone);

    // Gestion des paramètres
    MetadonneesInstruction &AjouterParametre(
        const gd::Chaine &type,
        const gd::Chaine &libelle,
        const gd::Chaine &informationSupplementaire = "",
        bool parametreOptionnel = false);

    // Informations de génération de code
    class InfoSupplementaire {
    public:
        gd::Chaine nomAppelFonction;
        gd::Chaine nomAppelFonctionAsynchrone;
        gd::Chaine type;
        std::vector<:chaine> fichiersInclus;
        // ... autres membres
    };
    InfoSupplementaire infoExtraCode;
};</:chaine>
Système de métadonnées de paramètres

Le système de métadonnées de paramètres est implémenté via la classe InfoParametre pour une transmission de paramètres de type sécurisé :

class InfoParametre {
public:
    enum Type {
        Chaine,
        Nombre,
        Booleen,
        Objet,
        Comportement,
        Expression,
        // ... autres types
    };

    // Définir la valeur par défaut du paramètre
    InfoParametre &SetValeurDefaut(const gd::Chaine &valeurDefaut);
    
    // Définir les informations supplémentaires du paramètre
    InfoParametre &SetInfoSupplementaire(const gd::Chaine &infoSupplementaire);
    
    // Obtenir les informations de type du paramètre
    const gd::Chaine &GetType() const;
};

Mode fournisseur de métadonnées

GDevelop utilise le mode FournisseurMetadonnees comme point d'entrée du système de réflexion :

class FournisseurMetadonnees {
public:
    // Obtenir les métadonnées de comportement
    static ExtensionEtMetadonnees<infocomportement> ObtenirExtensionEtInfoComportement(
        const gd::Plateforme& plateforme, gd::Chaine typeComportement);

    // Obtenir les métadonnées d'instruction
    static ExtensionEtMetadonnees<infoinstruction> ObtenirExtensionEtInfoAction(
        const gd::Plateforme& plateforme, gd::Chaine typeAction);

    // Obtenir les métadonnées d'expression
    static ExtensionEtMetadonnees<infoexpression> ObtenirExtensionEtInfoExpression(
        const gd::Plateforme& plateforme, gd::Chaine typeExpr);
};</infoexpression></infoinstruction></infocomportement>

Implémentation des informations de type à l'exécution (RTTI)

Mécanisme d'identification des types

Le système RTTI de GDevelop utilise des identifiants de chaîne pour l'identification des types :

Catégorie de type Format d'identifiant Exemple
Type d'objet Nom de l'objet "Sprite", "Texte"
Type de comportement Nom du comportement "ComportementPlateforme", "ComportementPhysique2"
Type d'instruction EspaceNom.NomInstruction "Sprite::SetX", "Texte::SetChaine"
Type d'expression EspaceNom.NomExpression "Sprite::X", "Variable::Global"

Requête dynamique de types

Les requêtes dynamiques de types sont effectuées via FournisseurMetadonnees :

// Requête d'informations sur le type d'objet
const InfoObjet& infoObj = FournisseurMetadonnees::ObtenirInfoObjet(
    plateforme, "Sprite");

// Requête d'informations sur le type de comportement  
const InfoComportement& infoComp = FournisseurMetadonnees::ObtenirInfoComportement(
    plateforme, "ComportementPlateforme");

// Requête d'informations sur l'instruction
const InfoInstruction& infoAction = FournisseurMetadonnees::ObtenirInfoAction(
    plateforme, "Sprite::SetX");

Mécanisme d'appels dynamiques

Appels dans l'exécution JavaScript

Dans l'exécution GDJS, les appels dynamiques sont implémentés via le mappage de noms de fonctions :

// Exemple d'appel de méthode d'objet à l'exécution
class ObjetExecution {
    // Point d'entrée d'appel dynamique
    executerAction(nomAction: string, parametres: any[]): boolean {
        const methode = this[nomAction];
        if (typeof methode === 'function') {
            return methode.apply(this, parametres);
        }
        return false;
    }

    // Implémentation spécifique de l'instruction
    SetX(x: number): boolean {
        this.x = x;
        return true;
    }

    SetY(y: number): boolean {
        this.y = y;
        return true;
    }
}

Support des appels asynchrones

Le système de réflexion de GDevelop prend en charge les opérations asynchrones via nomAppelFonctionAsynchrone :

// Définir le nom de la fonction asynchrone
infoInstruction.SetNomFonctionAsynchrone("asyncSetTexture");

// Implémentation asynchrone dans l'exécution JavaScript
class ChargeurTexture {
    async asyncSetTexture(objet: ObjetExecution, nomTexture: string): Promise<void> {
        const texture = await this.chargerTexture(nomTexture);
        objet.setTexture(texture);
    }
}</void>

Combinaison de la génération de code et de la réflexion

Générateur de code d'événements

GenerateurCodeEvenements est responsable de la conversion des métadonnées en code réel :

class GenerateurCodeEvenements {
public:
    // Générer le code des paramètres
    gd::Chaine GenererCodeParametre(
        const gd::InfoParametre& metadonnees,
        const gd::Chaine& valeurParametre);
    
    // Générer le code d'appel de fonction
    gd::Chaine GenererAppelFonction(
        const gd::InfoInstruction& infosInstr,
        const std::vector<:chaine>& codesParametres);
};</:chaine>

Génération de code de type sécurisé

Le processus de génération de code garantit la sécurité des types :

// Traitement de paramètres de type sécurisé
gd::Chaine GenerateurCodeEvenements::GenererCodeParametre(
    const gd::InfoParametre& metadonnees,
    const gd::Chaine& valeurParametre) {
    
    switch (metadonnees.GetType()) {
        case "nombre":
            return "parseFloat(" + valeurParametre + ")";
        case "chaine":
            return valeurParametre; // Utiliser directement la chaîne
        case "objet":
            return "sceneExecution.getObjects(" + valeurParametre + ")";
        case "comportement":
            return "obtenirComportement(" + valeurParametre + ")";
        default:
            return valeurParametre;
    }
}

Cas pratiques d'application

Développement d'extensions personnalisées

Développer des extensions personnalisées via le système de réflexion :

// Enregistrer un nouveau type d'objet
void MonExtension::EnregistrerObjets() {
    gd::InfoObjet& obj = AjouterObjet("ObjetPersonnalise",
        _("Objet Personnalise"),
        _("Un objet personnalisé avec des capacités spéciales"),
        "CppPlatform/Extensions/objetpersonnalise.png");
    
    // Ajouter une action
    obj.AjouterAction("ActionSpeciale",
                 _("Effectuer une action spéciale"),
                 _("Effectuer une action spéciale sur l'objet"),
                 _("l'action spéciale"),
                 _("Objet Personnalise"),
                 "CppPlatform/Extensions/actionspeciale.png",
                 "CppPlatform/Extensions/actionspeciale_noir.png")
        .AjouterParametre("objet", _("Objet"), "ObjetPersonnalise")
        .AjouterParametre("nombre", _("Niveau de puissance"))
        .SetNomFonction("executerActionSpeciale");
}

Modification dynamique des comportements

Utiliser la réflexion pour modifier les comportements à l'exécution :

// Ajouter dynamiquement un comportement à un objet
function ajouterComportementDynamique(objet: ObjetExecution, typeComportement: string) {
    const infoComp = gdjs.FournisseurMetadonnees.ObtenirInfoComportement(
        gdjs.JeuExecution.getPlateforme(), typeComportement);
    
    if (infoComp) {
        const comportement = objet.ajouterComportement(typeComportement);
        // Initialiser les paramètres du comportement à partir des métadonnées
        initialiserComportementDepuisMetadonnees(comportement, infoComp);
    }
}

Stratégies d'optimisation des performances

Mise en cache des métadonnées

GDevelop utilise un mécanisme de mise en cache des métadonnées pour améliorer les performances :

// Implémentation du cache de métadonnées
class CacheMetadonnees {
private:
    static std::map<:chaine infoinstruction=""> cacheActions;
    static std::map<:chaine infocomportement=""> cacheComportements;
    
public:
    static const InfoInstruction& ObtenirInfoAction(
        const gd::Plateforme& plateforme, const gd::Chaine& typeAction) {
        auto it = cacheActions.find(typeAction);
        if (it != cacheActions.end()) {
            return it->second;
        }
        // Charger depuis les extensions de la plateforme et mettre en cache
        const auto& metadonnees = plateforme.ObtenirInfoAction(typeAction);
        cacheActions[typeAction] = metadonnees;
        return metadonnees;
    }
};</:chaine></:chaine>

Mécanisme de chargement différé

Pour les métadonnées rarement utilisées, un chargement différé est adopté :

// Implémentation du chargement différé
class ChargeurMetadonneesDiffere {
public:
    const InfoInstruction& ObtenirInfoInstruction(const gd::Chaine& type) {
        if (!metadonneesChargees) {
            ChargerMetadonnees();
            metadonneesChargees = true;
        }
        return metadonneesCachees;
    }
};

Sécurité et gestion des erreurs

Validation des types

Effectuer une validation des types avant les appels dynamiques :

// Fonction de validation des paramètres
function validerParametres(metadonnees: InfoInstruction, parametres: any[]): boolean {
    if (parametres.length !== metadonnees.ObtenirNombreParametres()) {
        return false;
    }
    
    for (let i = 0; i < parametres.length; i++) {
        const infoParam = metadonnees.ObtenirParametre(i);
        if (!estTypeValide(parametres[i], infoParam.GetType())) {
            return false;
        }
    }
    return true;
}

Mécanisme de récupération d'erreurs

Implémenter une récupération d'erreurs robuste :

// Enveloppe d'appel sécurisé
gd::Chaine AppelFonctionSur(const InfoInstruction& metadonnees,
                           const std::vector<:chaine>& parametres) {
    try {
        if (ValiderParametres(metadonnees, parametres)) {
            return GenererAppelFonction(metadonnees, parametres);
        } else {
            return GenererCodeGestionErreur(metadonnees, parametres);
        }
    } catch (const std::exception& e) {
        return GenererCodeGestionException(e.what());
    }
}</:chaine>

Étiquettes: GDevelop réflexion RTTI appels dynamiques métadonnées

Publié le 19 juillet à 18h55