Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




AI C++ scripting for Yeti

Franceza




AI C++ scripting

for

Yeti

Jérôme Hubert

Grégory Pageot

Revision 26 - 29.01.07

TOC \o "1-3" \h \z \u

Introduction PAGEREF _Toc157845883 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380033000000

Utilisation PAGEREF _Toc157845884 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380034000000

Eléments requis PAGEREF _Toc157845885 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380035000000

Génération des sources PAGEREF _Toc157845886 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380036000000

Utilisation de la lib générée PAGEREF _Toc157845887 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380037000000

Utilisation sous debugger PAGEREF _Toc157845888 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380038000000

Procédure PAGEREF _Toc157845889 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800380039000000

Traces sous Debugger PAGEREF _Toc157845890 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390030000000

Problèmes connus de syntaxe PAGEREF _Toc157845891 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390031000000

Fonctionnalités non émulées PAGEREF _Toc157845892 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390032000000

Robot de compilation PAGEREF _Toc157845893 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390033000000

Génération des sources PAGEREF _Toc157845894 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390034000000

Architecture AI lib / moteur PAGEREF _Toc157845895 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390035000000

Généralités PAGEREF _Toc157845896 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390036000000

API moteur / API de l'AI lib PAGEREF _Toc157845897 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390037000000

Initialisation de la lib PAGEREF _Toc157845898 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390038000000

Emulation des scripts en C++ PAGEREF _Toc157845899 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003800390039000000

Description des différentes API entre moteur et lib PAGEREF _Toc157845900 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300030000000

API moteur spécifique au système de lib AI PAGEREF _Toc157845901 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300031000000

API fournie par la DLL au moteur PAGEREF _Toc157845902 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300032000000

API des AI fonctions PAGEREF _Toc157845903 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300033000000

Séparation du comportement et des variables PAGEREF _Toc157845904 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300034000000

Modèles PAGEREF _Toc157845905 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300035000000

Variables d'instance PAGEREF _Toc157845906 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300036000000

Variables de modèle PAGEREF _Toc157845907 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300037000000

Model cast PAGEREF _Toc157845908 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300038000000

Gestion des structs PAGEREF _Toc157845909 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900300039000000

Interruption de l'interprétation PAGEREF _Toc157845910 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310030000000

Interruption du flux d'exécution PAGEREF _Toc157845911 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310031000000

Intégration PAGEREF _Toc157845912 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310032000000

Gestion des d'états PAGEREF _Toc157845913 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310033000000

Interfaçage des états PAGEREF _Toc157845914 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310034000000

Changements d'état PAGEREF _Toc157845915 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310035000000

Metafonctions PAGEREF _Toc157845916 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310036000000

Exécution conditionnelle : Every, EnterState PAGEREF _Toc157845917 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310037000000

Fonctions scripts PAGEREF _Toc157845918 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310038000000

Contexte courant PAGEREF _Toc157845919 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900310039000000

Fonctions générées depuis les fonctions scripts PAGEREF _Toc157845920 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320030000000

AI fonctions de Yeti PAGEREF _Toc157845921 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320031000000

Optimisation PAGEREF _Toc157845922 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320032000000

Type object PAGEREF _Toc157845923 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320033000000

Type Any PAGEREF _Toc157845924 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320034000000

String et unistring PAGEREF _Toc157845925 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320035000000

Strings locales / strings 12512g62m membres PAGEREF _Toc157845926 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320036000000

Stratégie du string manager pour les scripts PAGEREF _Toc157845927 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320037000000

Stratégie du string manager pour le code C++ PAGEREF _Toc157845928 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320038000000

Cohabitation avec les interruptions d'interprétation PAGEREF _Toc157845929 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900320039000000

Bool array PAGEREF _Toc157845930 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330030000000

Maths classes PAGEREF _Toc157845931 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330031000000

Références sur ressources moteur PAGEREF _Toc157845932 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330032000000

Modifications de l'interpréteur PAGEREF _Toc157845933 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330033000000

Divers PAGEREF _Toc157845934 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330034000000

gcc PAGEREF _Toc157845935 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330035000000

Precompiled header .gch PAGEREF _Toc157845936 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330036000000

Compilation PAGEREF _Toc157845937 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330037000000

Link PAGEREF _Toc157845938 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330038000000

Options de compilation PAGEREF _Toc157845939 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900330039000000

PRX PAGEREF _Toc157845940 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340030000000

Profiling PAGEREF _Toc157845941 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340031000000

Outils PAGEREF _Toc157845942 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340032000000

Utilisation du profiler pour l'AI sur Xbox360 PAGEREF _Toc157845943 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340033000000

Utilisation de Code Analyst sur PC PAGEREF _Toc157845944 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340034000000

Outils de profiling intégrés PAGEREF _Toc157845945 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340035000000

Statistiques PAGEREF _Toc157845946 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340036000000

Performance PAGEREF _Toc157845947 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340037000000

Taille des libs PAGEREF _Toc157845948 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340038000000

Temps de compilation PAGEREF _Toc157845949 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900340039000000

Evolutions PAGEREF _Toc157845950 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350030000000

Court terme : avec le système actuel de génération PAGEREF _Toc157845951 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350031000000

Confort d'utilisation PAGEREF _Toc157845952 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350032000000

Optimisations PAGEREF _Toc157845953 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350033000000

Long terme : développement Gameplay programming en C++ PAGEREF _Toc157845954 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350034000000

Editeur et réflexivité pour les classes AI PAGEREF _Toc157845955 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350035000000

Références sur data PAGEREF _Toc157845956 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350036000000

Amélioration de syntaxe par rapport au code généré actuel PAGEREF _Toc157845957 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350037000000

String PAGEREF _Toc157845958 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350038000000

Modifications du pattern editor PAGEREF _Toc157845959 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900350039000000

Support multi DLLs PAGEREF _Toc157845960 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900360030000000

Migration PAGEREF _Toc157845961 \h 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003100350037003800340035003900360031000000

Introduction

Confronté à la nécessité d'améliorer les performances de Yeti pour la PS3, il est apparu que l'interprétation des scripts AI, bien qu'optimisée, reste relativement gourmande.

Une possibilité pour éviter ce problème est de développer le système d'AI en C++, mais il n'est pas envisageable de re-développer toutes les AIs existantes.

L'idée intermédiaire consistant à générer automatiquement une libraire C++ à partir des scripts AI de Yeti a donc été envisagée. Dans cette optique, le code C++ généré doit reproduire fidèlement le comportement de l'interpréteur.

Bienque l'exécution du code C++ ne soit pas optimale en raison du temps utilisé pour émuler l'interprétation des scripts, nous attendons un gain de performance important. D'après les benchs effectués préalablement sur Yeti, l'interprétation des scripts représente environ 60% du raster total de l'AI.

Utilisation

Les sources C++ sont créés à partir des scripts en post-process. Au niveau archivage, les scripts restent la référence.

Eléments requis

Un certain nombre de sources ne sont pas générés et il faut les récupérer préalablement via Perforce :

  • les fichiers archivés dans engine\exe\DLL
  • le seul fichier commun avec le moteur (ou l'éditeur) Yeti : engine\AIengine\AI_DLLInterface.h

F     Pour les Gpp n'ayant pas accès aux sources le mieux est de fournir une copie de AI_DLLInterface.h via le batch de livraison de la version utilisateurs en le copiant dans engine\exe\DLL\TPL

Génération des sources

Pour générer les sources des AIs il faut utiliser l'éditeur Yeti. Dans l'éditeur d'AI, dans le menu Build > Generate DLL sources choisir l'option :

"official maps (default)" pour créer les AIs pour toutes les maps finales du jeu (spécifiées dans la maps list) en excluant le code des patterns des maps test

"whole bigfile (default)" pour créer toutes les AIs contenues dans le big file (plus lent)

"current map (default)" pour créer les AIs de la map courante (plus rapide). Il faut avoir chargé une map pour utiliser cette option.

L'éditeur génère les fichiers sources correspondant aux scripts (en général un fichier .h + .cpp par modèle) ainsi que le projet Visual AIDLL.vcproj permettant de les compiler. Ces fichiers sont générés dans le répertoire exe\DLL\SRC pour les sources et exe\DLL\Make pour le vcproj.

F     Pour compiler la lib AI, les sources du moteur ne sont pas nécessaires (excepté AI_DLLInterface.h)

F     Le fichier AIDLL.vcproj généré comprend les trois plateformes PC, Xbox 360 et PS3 en target Release et Debug.

F     Lorsque les fichiers sources ont déjà été générés, seuls les sources comportant des différences sont mis à jour. Pour ce faire, les fichiers sont générés temporairement sous le nom fichiersource~, si ce fichier est différent du fichier fichiersource précédent, il le remplace sinon il est effacé. De cette manière seuls les fichiers modifiés sont mis à jour ce qui garantit un rebuild minimal avec le compilateur C++.

Utilisation de la lib générée

Pour utiliser la lib une fois compilée :

Dans l'éditeur il faut valider l'option Build ->UseDLL puis démarrer le jeu (GO). Lorsque le jeu est stoppé, la DLL est déchargée et peut donc être recompilée sans quitter l'éditeur. La commutation du DLL / scripts provoque le redémarrage du jeu. L'éditeur cherche en priorité la lib finale AI_PC_F.dll, puis la lib release AI_PC_R.dll puis la lib debug AI_PC_D.dll. De cette manière il est possible de panacher l'utilisation des libs debug, release, final avec un même éditeur (release ou debug) sans avoir à le recompiler ni même le redémarrer. En revanche il faut être attentif à renommer ou effacer la lib finale si l'on veut tester la lib release par exemple.

Sur XBox 360, le fonctionnement se rapproche du fonctionnent sur PC. Les libs sont générées sous forme de DLL (extension .XEX). Au lancement le moteur cherche en priorité la lib finale AI_X360_F.XEX, puis release AI_X360_R.XEX puis debug AI_X360_D.XEX. Si aucune lib n'est trouvée, le moteur se lance en mode scripts.

F     En mode moteur C++, le byte code script n'est pas chargé pour économiser de la mémoire.

Sur PS3, la lib est intégrée en link statique dans le moteur.

Pour cela dans le projet MainEngine dans le folder AILibs, il faut :

dé-valider exclude from build dans les propriétés de Visual sur l'AI lib que vous voulez linker

valider exclude from build dans les propriétés de Visual sur la lib AICdisable.lib

Lorsque le jeu est en mode C++, un logo "C++ version" apparaît en haut à gauche de l'écran sur les versions de moteur non FINAL. version dépend de la lib AI C++ utilisée :

d pour la lib debug

r pour la lib release

f pour la lib final

Utilisation sous debugger

Procédure

Pour utiliser le debugger de Visual ou ProDG :

Il est possible de debugger la librairie en utilisant le projet principal de Yeti (puis en plaçant des breakpoints directement dans les sources de la librairie et/ou en traçant à partir de l'interpréteur de scripts la fonction pst_Interpret).

Note : dans certains cas, lors de l'utilisation de la DLL sous l'éditeur, le debugger de Visual 2003 garde le fichier .pdb ouvert même lorsque celle-ci est déchargée : cela interdit la recompilation de la DLL (nous n'avons pas cherché la solution à ce problème). Nous n'avons pas rencontré ce problème avec Visual 2005.

Pour les personnes ne disposant pas des sources du moteur, il est possible d'utiliser le projet AIDLL.vcproj généré pour la lib. Pour ce faire il faut connecter le debugger de Visual sur le process de l'éditeur Yeti lancé préalablement via l'explorer (Debug/Attach to process => sélectionner Yeti_ game_platform.exe).

Note : pour pouvoir connecter le debugger de Visual sur l'éditeur Yeti, il faut désactiver le CrashReporter. En effet le CrashReporter s'enregistre comme debugger auprès de l'application ce qui empêche de connecter le debugger de Visual par la suite (dans ce cas Yeti apparaît en grisé dans la liste des process dans Visual). Pour ce faire, il suffit de renommer CrashReporter.exe avant de lancer Yeti.

Le debugger reste connecté au process tant que vous n'en sortez pas (par shift+F5 notamment). Contrairement aux exes, il est possible de rebuilder sans stopper le debugger. Après un rebuild il est parfois nécessaire de réafficher l'interface du debugger avec Alt+3 par exemple (cela dépend de la configuration de vos fenêtres). De fait il n'est jamais nécessaire d'effectuer de sortir (Shift+ F5).

Si vous voulez absolument sortir du debugger (Shift+F5), il est important de stopper le moteur Yeti au préalable afin de décharger la DLL, dans le cas contraire cela plante Yeti.

Les fichiers générés sont placés dans le répertoire DLL\SRC. Dans le projet .vcproj le générateur reproduit les chemins d'accès définis dans Yeti pour plus de clarté :

Traces sous Debugger

Interception des Assertions

Il est possible d'intercepter les AICheck sous debugger sans avoir les sources du moteur. Pour ce faire, les AICheck exécutent le callback AI_cl_DLL::AICheckCallback dans la DLL (fichier AI_DLLEntry.cpp).

Les AI_ASSERT de la DLL exécutent également ce callback.

Accès aux structures de données

Les identifiants générés dans la DLL sont dans un namespace AIDLL pour éviter les collisions avec le moteur en link statique. Sous le debugger il est nécessaire d'ajouter AIDLL:: dans les types visualisés dans la fenêtre watch pour que cela fonctionne.

Lorsque vous tracez le code d'un modèle, les données qui y sont attachées sont accessibles :

o       me est le gao courant

o       This() ou

(AIDLL::NomModel_Data*) mpc_VarInstance

visualise les variables d'instance courantes

o       ou pour une variable précisément

((AIDLL::NomModel_Data*) mpc_VarInstance)->NomVariable

Vous pouvez ouvrir directement tous les pointeurs de type object (notamment me) : cela permet d'accéder au nom d'un gao lorsque vous débuggez avec la version éditeur ou la clé de big file avec la version moteur.

Pour accéder aux données d'un object model casté :

o       Si dans le script, vous utilisez la forme : object oObj = (NomModel) obj;

(AIDLL::NomModel_Data*)oObj.m_pGenModel->

mpc_InstanceVar

visualise les variables d'instances

oObj.m_pGenModel->me est le gao

o       Si dans le script, vous utilisez la forme :

(NomModel) obj.var1 = 0;

(NomModel) obj.var2 = 0;

une fois le model cast exécuté et tant qu'aucun autre model cast n'est exécuté :

(AIDLL::NomModel_Data*)AIDLL::AI_cl_DLL::ms_oDLL.

m_ModelCastCache.m_pInstanceVar visualise les variables d'instances

AIDLL::AI_cl_DLL::ms_oDLL.m_ModelCastCache.me est le gao

F     Lorsque vous tracez finement le code d'un modèle qui utilise ce type de cast, il est conseillé de transformer le code script dans la forme précédente qui est plus fiable pour le debug et qui est plus performante en termes de temps d'exécution.

F     Les accesseurs inline ( This() notamment ) peuvent être utilisés dans les watch du debugger lorsque la DLL est compilée en Debug. A noter que sous Visual 2005 (contrairement à VC6 et 2003), à chaque step, le debugger invalide les entrées correspondantes qui passent en grisées. Le bouton permet de rafraichir leur état.

Note : dès que possible nous essaierons d'utiliser les fonctionnalités de customisation du debugger de Visual 2005 pour simplifier les syntaxes à appliquer dans les fenêtres de watch

Contrôle de l'exécution

Sur la version moteur utilisant le link dynamique (Xbox360, PC), un système permet de décharger puis recharger la DLL à chaud pendant l'exécution comme sous l'éditeur. Il est commandé par une variable accessible dans le debugger. La procédure est la suivante :

Au préalable :

Lancer le moteur en mode C++

Ouvrir le projet AIDLL.vcproj sous Visual et attacher le debugger sur Yeti

Debugger à votre convenance...

Déchargement de la DLL :

Pour décharger la DLL : en étant breaké, afficher l'objet AIDLL::AI_cl_DLL::ms_oDLL dans la watch window

Déplier l'objet ms_oDLL, sélectionner l'adresse du pointeur membre mp_bUnloadDLLRequest et la drag and droper dans la case vide de la watch window. Caster cette adresse en (bool*)

Changer la valeur pointée par cette adresse de false à true puis relancer le jeu (F5)

F     A ce stade la DLL est déchargée et le jeu est gelé

F     Sans détacher le debugger, opérer les modifications souhaitées sur la DLL (recompiler, renommer la DLL pour sélectionner une autre target...) 

Rechargement de la DLL :

Pour recharger la DLL et relancer, dans le debugger effectuer un "Break All" (menu Debug). Le debugger vous annonce que le code n'est pas visible => ce n'est pas gênant, ignorez cet avertissement

Repasser la valeur pointée par l'adresse dans la watch window de true à false puis relancer le jeu (F5)

F     A ce stade le jeu recharge la DLL disponible et continue l'exécution

F     Si aucune DLL n'est disponible le moteur repositionne la variable à true et se replace en état d'attente : dans ce cas, réitérez la procédure de rechargement (le byte code script n'étant pas chargé il faut obligatoirement une DLL pour relancer.)

F     La DLL rechargée doit conserver les mêmes définitions de classe que les classes scripts actuellement chargées par le moteur.

F     Si vous debuggez à partir des sources moteur vous pouvez changer directement la valeur du flag AI_cl_DLLManager::m_bUnloadDLLRequest (sans avoir à mapper l'adresse du booléen dans la fenêtre de watch).

Pour récupérer la main sur l'éditeur lorsque la DLL est sur un plantage :

o       placer cette expression dans la watch window du debugger :

(AIDLL::AI_cl_DLL::ms_oDLL).mp_bCSuspended

o       passer sa valeur de false à true

o       ressortez du code fautif en sautant la partie de code posant problème grâce à l'option "set next statement" du debugger

o       continuer l'exécution

F     Le flag modifié court-circuite l'exécution des AIs dans l'éditeur. Pour réactiver les AIs C++ il faut appuyer sur stop / go ou redémarrer la map.

Stack dumps

AI_Check & AI_ASSERT

Sur Xbox360 quand un AI_Check (moteur) ou un AI_ASSERT (DLL) se déclenche, la stack courante est dumpée dans le log sous forme d'adresses hexa. Si l'outil XMW est connecté sur le moteur ces stacks sont traduites via les fichiers pdb sous forme de noms de fonction.

Crash dump

Lorsqu'un crash se produit, la stack courante est dumpée sous forme de noms de fonction. En général le nom de la fonction courante est suivi d'un offset caractérisant la ligne de code exacte ayant produit le crash.

Pour déterminer la ligne C++ exacte à partir de cet offset sous Visual :

Ouvrir le projet AIDLL.sln, et sélectionner la target correspondante

Modifier les propriétés du fichier .cpp incriminé comme suit

(C++>Output Files>Assembler Output> Assembly, Machine Code and Source)

  • Sur ce fichier .cpp, faire clic droit > Compile
  • Un fichier .cod du même nom est produit dans le répertoire DLL\Make\<TargetSélectionnée>
  • Ouvrir le fichier .cod, rechercher la fonction ayant produit le crash. La première colonne dans le fichier .cod est l'offset hexa du byte code => chercher la ligne correspondant à l'offset fourni par le stack dump en faisant la différence par rapport au début de la fonction

Exemple

PROC ; AIDLL::DroneShaderSeeker::DroneShaderSeeker_20_PrepareDroneOnceSpawned

; _this$ = ecx

:

:

: This()->mo_Drone = model_instance<ObjectSpawner>( This()-

003f4 8b 8d 78 ff ff

ff mov ecx, DWORD PTR _this$[ebp]

003fa e8 00 00 00 00 call ?This@DroneShaderSeeker@AIDLL@@QBEPAVDroneShaderSeeker_Data@2@XZ ; AIDLL::DroneShaderSeeker::This

003ff 8b 50 1c mov edx, DWORD PTR [eax+28]

00402 52 push edx

00403 8d 4d d8 lea ecx, DWORD PTR $T152224[ebp]

00406 e8 00 00 00 00 call ??0?$model_instance@VObjectSpawner@AIDLL@@@AIDLL@@QAE@PAVgame_object@@@Z ; AIDLL::model_instance<AIDLL::ObjectSpawner>::model_instance<AIDLL::ObjectSpawner>

0040b 8b c8 mov ecx, eax

0040d e8 00 00 00 00 call ??C?$model_instance@VObjectSpawner@AIDLL@@@AIDLL@@QAEPAVObjectSpawner@1@XZ ; AIDLL::model_instance<AIDLL::ObjectSpawner>::operator->

00412 8b c8 mov ecx, eax

00414 e8 00 00 00 00 call ?oGetSpawnedObject@ObjectSpawner@AIDLL@@QAEPAVgame_object@@XZ ; AIDLL::ObjectSpawner::oGetSpawnedObject

00419 8b f0 mov esi, eax

0041b 8b 8d 78 ff ff

ff mov ecx, DWORD PTR _this$[ebp]

00421 e8 00 00 00 00 call ?This@DroneShaderSeeker@AIDLL@@QBEPAVDroneShaderSeeker_Data@2@XZ ; AIDLL::DroneShaderSeeker::This

00426 89 70 20 mov DWORD PTR [eax+32], esi

: if( This()->mo_Drone!= NULL)

Problèmes connus de syntaxe

Il existe quelques problèmes de syntaxe difficiles à corriger au niveau de la génération et qui requièrent une adaptation des scripts.

Certains cas sont détectés automatiquement à la compilation des scripts grâce à des vérifications supplémentaires ajoutées au compilateur produisant des warnings.

D'autres cas sont difficiles à détecter lors de la compilation script. Pour gérer ces problèmes, le déploiement du système de génération + compilation des sources AI C++ sur un robot de compilation permet de fournir un retour rapide aux équipes de Gameplay programmeurs sans que ceux-ci aient à utiliser Visual systématiquement pour vérifier leurs scripts.

Sur le robot la lib est générée en un seul metafile .cpp (environ 650000 lignes) puis elle est compilée en target debug sous Visual. Ce mode de compilation est le plus rapide (1 min 30 pour un rebuild complet) ce qui est un avantage pour l'utilisation avec le robot.

Note : la version actuelle du debugger de Visual ne gère pas correctement les sources de plus de 65535 lignes.

Note : gcc est moins tolérant que Visual : certaines erreurs n'apparaissent que sous gcc.

Quelques exemples de problèmes syntaxiques :

La portée des variables locales est différente en script et en C++. Par exemple dans le cas d'un switch, les déclarations de variables sont valides d'un case à l'autre en script. En C++ pour éviter certaines erreurs de compilation le code de chaque case est placé entre accolades. De ce fait, les variables définies dans un case ne sont pas valides dans un autre. Ce cas est détecté à la compilation de scripts et produit un warning. La solution dans ce genre de cas est de remonter la déclaration de variable plus haut dans le code de la fonction.

L'émulation du système de metafonctions en C++ utilise des goto (cf Metafonctions 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003300320036003800350030000000 ). La portée de ces gotos croise des déclarations de variables pouvant produire une erreur en C++. Ce type de cas n'est pas détecté à la compilation des scripts et seule une compilation du code C++ généré permet de les mettre en évidence. gcc est plus sensible que Visual sur ce sujet. Pour obtenir un retour équivalent avec Visual nous avons transformé le warning 4533 en erreur via un pragma.

Les string et unistring sont passées sous forme de const string& et const unistring& dans les fonctions générées depuis les scripts. En effet les paramètres de fonction sont "input only" en script. Pour des raisons d'efficacité il est donc plus intéressant de les passer sous forme de référence (sans le const le code ne compile pas sous gcc dans le cas où la string entrante est issue du retour d'une fonction). A la compilation il s'est avéré que quelques (rares) fonctions scripts utilisaient les strings entrantes en read / write.

Note : la portée des variables définies dans les boucles for ( for (int i = 0.) ) en scripts correspond à l'ancienne norme C++. Dans Visual et gcc il existe une option permettant de compiler en utilisant cette norme plutôt que la nouvelle norme incompatible (Force Conformance in For Loop Scope = No, -fno-for-scope). Cela évite de devoir effectuer un grand nombre d'adaptations dans les scripts.

Fonctionnalités non émulées

Les fonctionnalités difficiles à émuler en C++ qui ne sont pas (ou peu) utilisées par les Gpps ont été passées en 'deprecated'. Des warnings sont générés à la compilation des scripts, ces fonctionnalités ne doivent plus être utilisées.

Le mot clé break sur n niveaux break(n); n'a pas été émulé. Pour éviter ce type de construction le plus simple est d'utiliser une variable locale.

.Avant

Après.

for (i = 0 ; i < 10 ; i++)

bool bExit = false;

for (i = 0 ; i < 10 ; i++)

}

if (bExit)

break;

Robot de compilation

Le robot de compilation permet à tous les GPP d'avoir un retour rapide sur la compatibilité C++ des archivages des scripts, notamment lorsqu'ils ne disposent pas de Visual sur leur machine. Pour ce faire, le mieux est d'installer cc-tray (voir avec votre responsable QATools).

Lorsque la tâche de build de l'AI lib (ici GRAW2_AI_DLL, GRAW2_AI_DLL_RebuildAll correspondant au night build) est en état "failure" :

Double cliquer sur la ligne correspondante

Une page HTML s'ouvre => cliquer sur le lien BuildLogs Folder

Explorer s'ouvre sur un répertoire : ouvrir le dernier log .xml qui y est généré afin de déterminer quelle est l'erreur de compilation (noter le fichier et le numéro de ligne notamment). Cette description d'erreur est également envoyée sous forme de mail par le robot.

Cliquer sur le raccourci :

L'explorer se trouve maintenant dans le répertoire Yeti du robot => aller dans DLL\SRC

Ouvrir le fichier .cpp contenant l'erreur de compilation à la ligne spécifiée

Identifier le modèle, la fonction (ou état), l'erreur dans le source C++ pour corriger le script correspondant.

Génération des sources

Les sources sont générés par la compilation des scripts dans un mode particulier générant le code C++ au fur et à mesure.

L'avantage de se placer à la compilation plutôt que générer les sources C++ à posteriori, à partir du bytecode est double :

  • cela permet d'obtenir un code non cabalistique ressemblant beaucoup aux scripts originaux
  • se placer au niveau langage permet (en théorie) un meilleur gain en performance en réalisant une émulation plus haut niveau du système de scripts

Note : la génération s'effectuant à la compilation, elle s'effectue sur le code script pré-processé :

nous réintégrons les commentaires dans le code généré

il est également possible de réintégrer les macros les plus simples pour obtenir un code plus clair. Actuellement cette option n'est pas utilisée car elle implique de ne pas avoir de macros de même nom définies différemment. D'autre part elle ne gère pas les macros définies dans les corps de fonction. Or ces deux cas sont fréquents dans les scripts existants.

Le code de génération a été isolé le plus possible du code du compilateur, de manière à ne pas alourdir celui-ci. L'intégration du source generator dans le compiler utilise un design pattern "observer" : cela peut permettre d'implémenter facilement un système bi-pass dans le futur.

Un certain nombre d'éléments syntaxiques systématiques de la génération sont définis en externe dans des fichiers pattern stockés dans le répertoire \exe\DLL\AIE_Patterns. Via ces patterns il est possible de modifier en partie la forme du code généré sans recompiler, ni même relancer l'éditeur (les fichiers pattern sont rechargés à chaque génération). Ces fichiers servent à la génération des .h, .cpp et .vcproj.

C     Par exemple, pour changer les options de compilation dans le fichier AIDLL.vcproj généré il suffit de modifier le pattern VC_Proj.txt.

Architecture AI lib / moteur

Généralités

Le code généré des AIs est placé dans une librairie ayant un minimum de dépendances avec le moteur ; le but premier est de pouvoir compiler la librairie sans avoir les sources du moteur (excepté le fichier AI_DLLInterface.h). Cette librairie utilise par ailleurs un ensemble minimaliste d'includes de la libc.

L'orientation choisie pour le développement est de modifier au minimum le système d'interprétation actuel pour minimiser les changements dans les scripts et AI fonctions existants.

Le code AI généré ne remplace que les scripts : il utilise la liste de fonctions AI du moteur de la même manière que les scripts.

Le code généré est placé dans un namespace AIDLL pour éviter tout problème de collision dans le cas où la lib est linkée en statique avec le moteur.

API moteur / API de l'AI lib

L'interface avec la lib AI est conçue pour minimiser les liens statiques avec le moteur de manière à permettre facilement de multiples cycles "chargement, plug à chaud, déchargement" sous l'éditeur lorsque la librairie est compilée sous forme de DLL.

Pour ce faire, les API mises en place spécifiquement pour l'AI lib utilisent un système inspiré de la technologie COM (du moteur vers la lib et inversement). Il s'agit d'interfaces purement abstraites exportant leur comportement via une liste de fonctions définies virtuelles. Une fonction permet d'obtenir l'adresse de l'interface ensuite, elle est manipulée de manière très simple en C++ sous forme d'objet.

Avec ce système, une seule fonction globale AI_BootDLL est exportée explicitement depuis la DLL. L'adresse de cette fonction est obtenue dans le moteur en utilisant la fonction win32 GetProcAddress dans le cas du link dynamique ou un simple extern AI_BootDLL lorsque la lib est linkée en statique. Ce point constitue la seule différence d'intégration entre les modes link statique / link dynamique car toutes les autres interactions moteur / AI lib s'effectuent via des interfaces virtuelles.

La table des AI fonctions constituant l'API fournie par le moteur aux scripts, forme un système d'indirection similaire (table de pointeurs de fonctions) même si le contrôle de typage dans ce cas, ne s'effectue pas "naturellement" avec le compilateur (cf. API des AI fonctions 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003000310039003300370034000000 ).

Initialisation de la lib

Lors du boot de la lib :

  1. Si le moteur est en mode dynamique il charge préalablement la DLL via la fonction LoadLibrary de win32
  2. Le moteur obtient l'adresse de la fonction AI_BootDLL via la fonction GetProcAddress ou via extern (selon la méthode de link).
  3. Le moteur appelle cette fonction en lui fournissant l'adresse des API du moteur utilisables depuis l'AI lib (tables des AI fonctions, AI_DLLengine API).
  4. La fonction AI_BootDLL initialise les références sur ressources moteur (requiert des adresses d'objets à partir de clés de big file) via une fonction de l'API du DLLengine puis retourne au moteur un pointeur sur l'API fournie par la DLL (AI_cl_DLLInterface).
  5. Le moteur poursuit le processus d'initialisation en obtenant via l'API de la DLL l'adresse des instances statiques de chaque classe de modèle. L'adresse de ces instances est vue sous forme d'interface abstraite AI_cl_GenModelInterface par le moteur et sera stockée dans chaque instance AI_cl_Model correspondante de l'interpréteur.

  1. Le moteur utilise ces interfaces de GenModel pour initialiser les adresses des variables de modèle (statiques) dans la lib. Il exécute également un système permettant de vérifier en debug que les adresses de variables d'instance entre moteur et lib correspondent (Visual est capable d'effectuer cette vérification à la compilation contrairement à gcc).

Note : le moteur initialise les GenModel dans un ordre en opérant par couche : il commence par les superclasses puis remonte par couche dans les arbres d'héritage.

C     Les détails d'implémentations concernant les différentes API sont traités dans le chapitre Emulation des scripts en C++ 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003000310039003200340038000000  

Emulation des scripts en C++

Le code C++ généré utilise un certain nombre de principes permettant de coller au système de scripts de Yeti. Dans ce chapitre, nous étudierons les aspects particuliers de l'émulation, tout ce qui est spécifique ou s'éloigne d'un code C++ traditionnel.

Description des différentes API entre moteur et lib

API moteur spécifique au système de lib AI

Pour pouvoir émuler le système de scripts, nous avons besoin d'exporter des services supplémentaires en complément des services standards fournis par les AI fonctions.

Dans un premier temps nous avions intégré ces services sous forme d'AI fonctions mais cela avait plusieurs inconvénients :

  • Placer des services dans les AI fonctions les rend disponibles pour les Gameplay Programmeurs, or ces services sont spécifiques au système de lib C++ et ne sont pas sensés être utilisés manuellement.
  • Une fois un service ajouté dans l'API, le rendre disponible nécessite une génération des sources (cf API des AI fonctions 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003000310039003300370034000000 ) ce qui n'est pas pratique en phase de développement.
  • Les types reconnus dans les prototypes d'AI fonctions sont limités et ne permettent pas d'utiliser toutes les configurations permises par le C++.
  • Les index d'AI fonctions étant définis par plage pour chaque domaine, le nombre d'AI fonctions disponibles est limité.

Les services fournis par cette API couvrent des aspects variés spécifiques à l'émulation.

API fournie par la DLL au moteur

La lib fournit une API globale (AI_cl_DLLInterface) qui permet notamment au moteur d'adresser les objets de la lib.

Les instances de modèle constituent un deuxième niveau d'API fournissant des interfaces (AI_cl_GenModelInterface) permettant d'initialiser puis d'exécuter le code équivalent à chaque modèle script.

API des AI fonctions

Méthode générale

Les AI fonctions sont stockées dans Yeti dans une grande table de pointeurs de fonctions définie sous forme void**. Le pointeur de cette table est importé à l'init et stocké sous forme globale publique dans la lib.

A ce stade, nous ne disposons pas du typage permettant d'appeler correctement ces fonctions depuis le code C++. Pour résoudre ce problème, le système de génération de sources dispose d'un système spécifique générant un fichier header AI_GenPrototypes.h comprenant les définitions de prototypes pour toutes les AI fonctions. Ce fichier est inclus dans le pre-compiled header de manière à être disponible dans tout le code généré.

Détails d'implémentation

Tout d'abord nous générons un typedef pour tout pointeur de fonction correspondant à une AI fonction. Cette définition de type permet d'appeler les pointeurs de fonction avec les bons paramètres correspondant à la fonction moteur.

Exemple

typedef void T_GFX_ViewportPickable_1361 ( uint32, bool);

Pour chaque AI fonction nous générons deux prototypes de fonctions utilisables dans le code généré.

La première forme exécute l'AI fonction sur l'objet courant

La deuxième forme (suffixée avec un _) exécute l'AI fonction sur un objet passé en paramètre et effectue les changements de contexte de gao nécessaires sur l'interpréteur.

inline void GFX_ViewportPickable(uint8 _par0, bool _par1)

inline void GFX_ViewportPickable_ (object _obj ,uint8 _par0, bool _par1)

Ces fonctions sont définies en inline pour éviter potentiellement les doubles appels lors de l'utilisation des AI fonctions.

D'autre part, ces fonctions permettent d'adapter les paramètres de certains types.

Cas particuliers et optimisations

Pour certaines AI fonctions, nous voulons pouvoir customiser le système de génération de prototypes. Pour ce faire, nous avons créé une deuxième version de la macro M_DefineFunction utilisée dans le moteur pour définir les AI fonctions : M_DefineFunctionEx

Cette version étendue de la macro permet d'ajouter un masque de bits caractérisant plus précisément la fonction lorsque cela est nécessaire.

Ce masque permet de définir que :

  • le prototype ne doit pas être généré automatiquement ce qui permet de l'implémenter manuellement dans le fichier AI_Prototypes.h.

En effet, dans certains cas particuliers nous devons customiser l'intégration de certaines AI fonctions.

D'autre part, cela permet de ré-implémenter certaines petites fonctions (notamment certaines fonctions maths) sous forme de fonction inline directement dans la lib ce qui évite un appel de fonction coûteux (lorsque le code est compilé avec les optimisations).

  • l'AI fonction correspondante n'utilise pas le contexte ou une partie du contexte courant de l'interpréteur de script : cela permet de réaliser un certain nombre d'optimisations (cf.Fonctions scripts 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003700300035003400330035000000 ).

D'autre part, une fonction qui n'utilise pas le contexte de gao, ne nécessite pas d'appel sous la forme objet.AIfonction. De fait il n'est pas nécessaire de générer le prototype de cette forme ce qui allège le code de AI_GenPrototypes.h

F     Un système de vérification s'assure en dynamique, lors de l'exécution des AI fonctions que ces flags sont positionnés à bon escient. Ces vérifications s'effectuent dans les DLL non FINAL couplées avec le moteur non FINAL sur Xbox360 ou en mode éditeur sur PC. Ce système de vérification de flags est également utilisé pour rasteriser les AI fonctions dans le moteur non FINAL. (cf AI_FUNCTION_CHECK_FLAGS et class _ai_function_flags_checker

F     Dans le cas où il y aurait un assert de type « AI function flag mismatch in . » à l'exécution, cela voudrait dire que la fonction qui pose problème serait mal flagée. C'est par exemple le cas pour les fonctions utilisant le string manager. Il faut dans ce cas indiquer qu'il a besoin d'un modèle. Dans la ligne suivante on indique que la fonction n'a pas besoin d'un GAO mais qu'elle a par contre besoin, implicitement, d'un modèle.

M_DefineFunctionEx(.,AI_E_FuncAttrib_DoNotNeedGAOContext)

  • l'AI fonction doit utiliser un suffixe de "décoration". En effet dans les AI fonctions existent quelques cas de collisions entre fonctions portant le même nom que le C++ ne sait pas différencier sans indétermination. Activer le système de décoration permet d'ajouter l'index de l'AI fonction au sein de la table des AI fonctions comme suffixe à son nom.

Exemple

inline uint32 COL_stGetRaycastAllReport_624(.

inline uint32 COL_stGetRaycastAllReport_624_(.

Ce nom suffixé est employé à la fois dans l'AI_GenPrototypes.h et dans le code généré à partir des scripts.

Note : au début du développement nous utilisions cette décoration systématiquement pour toutes les AI fonctions, cependant cela nuit à la lisibilité du code généré. Exploiter le système de flags nous a permis de réduire son utilisation aux quelques cas qui posent problème.

  • l'AI fonction peut provoquer une interruption de l'interprétation (cf 5.5 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003300320037003000330039000000 ). Dans ces cas nous générons un encart supplémentaire dans le prototype testant le flag correspondant de l'interpréteur pour interrompre l'exécution le cas échant. Exemple :

typedef void T_AI_EnableTrack_40 ( uint32, bool);

inline void AI_EnableTrack( uint8 _par0, bool _par1)

inline void AI_EnableTrack_ (object _obj ,uint8 _par0, bool _par1)

AI_cl_DLL::ms_oDLL.mp_DLLEngineInterface

->Interpretor_SetReferencedObject(pObjectBackup);

avec

inline void AI_StopInterpretation ()

  • l'AI fonction ne sert qu'au debug et peut être remplacée par une macro vide en target FINAL (DBG_Print par exemple)

Séparation du comportement et des variables

Les espaces mémoire des variables d'instances et des variables de modèles continuent à être gérés par l'interpréteur. Pour garder ce principe, le code est réparti en deux classes C++ par modèle (au lieu d'une classe dans un programme C++ habituel). La première classe contient le comportement (code) du modèle (GenModel), la seconde sert à mapper les données du modèle (GenModel_Data). De fait, dans le code généré, les accès aux variables utilisent une indirection supplémentaire via un pointeur mpc_InstanceVar.

Modèles

Outre le pointeur sur les variables d'instance, la classe AI_GenModel comporte un pointeur sur le gao correspondant nommé me (cf Type object 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003100350030003900390039000000 ).

Toutes les classes dérivées de AI_GenModel possèdent des méthodes virtuelles (possèdent un pointeur sur vftable).

Une instance statique de chaque classe générée NomModel est créée par défaut. Lorsque l'interpréteur invoque le code d'un état c'est cette instance de classe modèle qu'il utilise via la méthode virtuelle Run. A l'appel du Run, il transmet l'adresse du gao et l'adresse des variables d'instance qui seront affectées au préalable dans l'instance.

Ces classes de modèle générées sont identifiées initialement dans la lib par les clés de big file correspondant aux instances de AI_cl_Model issues du moteur. Les associations avec ces clés sont générées dans le code de l'AI lib. A l'init de la lib, les clés de bigfile permettent d'obtenir les adresses des instances statiques de chaque classe générée. Cette adresse d'instance C++ est alors stockée de manière générique côté interpréteur dans la classe AI_cl_Model. Cela permet d'accéder très rapidement au code C++ correspondant à une classe script.

Chaque classe modèle générée possède un système d'identification statique et dynamique (virtuel) différent des clés de big file et disponible après init de la lib :

  • la variable statique publique msp_Model contient l'adresse du modèle moteur (de manière opaque sous forme void*). Définie en statique, elle est utilisable sans pointeur d'instance.
  • la fonction virtuelle GetType() renvoie le msp_Model. Etant une fonction virtuelle elle nécessite une instance de travail courante. Par contre elle permet d'identifier le type de l'instance utilisée même si l'on se trouve dans le code d'une de ses superclasses.

Variables d'instance

Les classes GenModel_Data réalisent un mapping mémoire rigoureusement équivalent à ce que fait l'interpréteur. Pour ce faire nous avons du trouver un dénominateur commun entre le compilateur de scripts, l'alignement fourni par Visual C++ et gcc. Pour ce faire :

nous avons ajouté quelques octets de padding dans le cas des héritages dans le compilateur de scripts

dans Visual nous utilisons des directives d'alignement pragma pack sur 4 octets et _declspec (align (4))

dans gcc nous utilisons la directive d'alignement __attribute__(align(4)) et nous ajoutons des octets de padding ( char__padding__[] ) dans les cas d'héritage

Note : ces classes réalisant un mapping mémoire rigoureux des buffers de variables de l'interpréteur, elles ne peuvent comporter de fonction virtuelle, ce qui aurait pour effet d'insérer un pfvtable qui casserait le mapping.

Le pointeur mpc_InstanceVar est casté dans le type courant par un accesseur inline This() défini pour chaque classe. Ainsi, This() renvoie toujours le type adéquat quelque soit la fonction ou état dans lequel on se trouve, y compris dans les cas d'héritage.

Le pointeur mpc_InstanceVar est affecté au préalable à chaque fois qu'un modèle est exécuté sur une nouvelle instance (lors de l'exécution d'un état par l'interpréteur ou lorsque l'on appelle une fonction via un objet model casté).

Les avantages de cette méthode de mapping sont :

  • la performance : l'affectation de toutes les variables d'instance courantes d'un objet s'effectue en une seule affectation de pointeur
  • la lisibilité du code généré et la simplicité pour générer ce code : hormis l'indirection via This(), l'utilisation des variables est similaire aux scripts

L'utilisation d'une variable d'instance de modèle dans le code généré depuis le script prend donc la forme :

This()->NomDeVariableScript

Variables de modèle

Les variables de modèle sont modélisées par des variables statiques de type référence en C++.

Dans un premier temps nous avons envisagé d'appliquer le même système de mapping que celui employé pour les variables d'instance en générant une seconde classe dédiée aux variables statiques. Cependant avec les variables statiques, ce système ne fonctionne pas dans le cas d'arbre d'héritage. Voici un contre exemple mettant en évidence ce problème :

  1. les variables de modèle à l'instar des variables statiques en C++ ont une instance unique pour chaque classe
  2. dans le cas d'un héritage :

class a ;

class b : public a;

class c : public a ;

. l'unicité de va, vb, vc fait qu'il n'est pas possible d'avoir à la fois va / vb et va / vc contigus en mémoire. De fait le système par mapping ne peut pas fonctionner.

Nous nous sommes donc acheminés vers un système consistant à définir les variables statiques sous forme de pointeur statique. Chaque variable de modèle correspond à un pointeur statique dans les classes GenModel_Data également utilisées pour les variables d'instance. Les aspects particuliers de notre implémentation sont :

  • plutôt que des pointeurs, nous utilisons des références afin de simplifier la génération du code depuis les scripts. En effet ajouter les * ou -> correctement dans le code généré n'est pas forcément simple en raison des priorités des différents opérateurs.
  • pour pouvoir assigner une référence après création nous utilisons une astuce consistant à les enfermer dans une structure :

struct S##VarName ;

pour utiliser une variable de modèle il suffit écrire NomDeVariable._

pour changer l'adresse de la référence nous utilisons une macro effectuant *((void**)&NomDeVariable) = nouvelle adresse

Note : il n'y a aucun cas particulier à gérer en génération car l'opérateur "." est le plus prioritaire en C++, donc il suffit d'ajouter systématiquement "._" au nom de variable à la génération

Note : dans un premier temps nous utilisions un système basé sur des union reference / pointeur void*, mais gcc refuse de placer une référence dans une union

Avec ce système :

  • la non contigüité des variables de modèles en mémoire ne pose pas de problème puisque leur adresse peut être fixée librement
  • affecter l'ensemble des variables statiques est beaucoup plus lourd qu'avec le système utilisé pour les variables d'instance en temps d'exécution (un pointeur par variable à affecter). Cependant ce n'est pas pénalisant car ce travail peut s'effectuer une seule fois à l'init de la lib AI, contrairement aux variables d'instance.
  • la génération est simple : il s'agit d'ajouter systématiquement "._" à chaque nom de variable
  • les variables statiques sont placées dans les classes générées GenModel_Data et profitent du modèle d'héritage issu des modèles généré pour les variables d'instance
  • les variables de modèle peuvent être accédées depuis des fonctions statiques contrairement aux variables d'instance. De fait, il n'est pas possible d'utiliser l'accesseur généré This() qui est relatif à l'instance. Nous générons donc des accesseurs statiques static inline Static() indépendants de l'instance. Ils sont chargés de résoudre les problèmes de cast pour accéder à la classe GenModel_Data adéquate.

static MyGenClass_Data* Static()

L'utilisation d'une variable de modèle dans le code généré depuis le script s'écrit :

Static()->NomDeVariableScript._

Model cast

Le model cast est émulé via une classe template model_instance<Type>.

Cette classe est chargée de réserver sur la pile les 12 octets nécessaires au stockage d'une classe modèle dérivée de AI_GenModel (gao, var instance, pvftable) et possède les services permettant d'affecter ces données en fonction du type de modèle et du gao requis (pour ce faire, elle utilise une fonction moteur dédiée).

A la construction d'un model_instance le code client fournit un gao et un type de destination du cast : model_instance<TypeCast> (gao)

La fonction moteur chargée de retrouver l'instance correspondante détermine le type effectivement instancié sur le gao (la classe fournie par le client peut être une superclasse du type utilisé). Cette fonction retourne le pointeur sur l'instance statique C++ associée à cette classe. Une fonction Clone des AI_GenModel permet de créer un objet du même type à l'adresse fournie par le model_instance via un new placement (ce système permet d'affecter le pvftable correspondant à l'instance dans l'espace réservé par le model_instance).

Gestion des structs

Dans le code généré à partir des scripts (déclaration locale, déclaration membre, paramètre de fonction script), une structure est définie comme le type NomModel_Data correspondant.

Dans le système de scripts, les structs sont des éléments faiblement typés. Pour l'interpréteur, une struct est un pointeur + une taille. De fait, certaines AI fonctions prévues pour recevoir ou renvoyer une struct peuvent s'interfacer avec n'importe quelle structure spécifique du code script.

Pour émuler ce comportement nous utilisons une classe intermédiaire struct_specifier contenant l'adresse et la taille de la structure à transmettre. Les AIs fonctions transmettant des structures utilisent ce type.

Chaque NomModel_Data généré possède un constructeur à partir de struct_specifier et un operateur de cast en struct_specifier.

Exemple

AI_GaoSwitch_Data (struct_specifier _rSpec)

operator struct_specifier ()

Ce système fonctionne mais sa particularité est de provoquer, dans le cas d'un retour de fonction, un accès à une zone de stack qui n'est plus dans la stack frame de la fonction courante. Cela fonctionne car l'accès s'effectue en même temps que le retour, néanmoins cela impose de désactiver les options de vérification de l'accès à la stack dans le compilateur (Basic Runtime Checks = Default sous Visual, -fno-stack-check sous gcc). Dans le cas contraire des exceptions se déclenchent.

En mode _DEBUG un CRC est ajouté dans le struct specifier : il est calculé lors de la création du struct_specifier puis vérifié lors de la lecture du struct_specifier par la classe destination. De cette façon nous nous assurons qu'aucun écrasement n'est intervenu lors de retour de fonction sur les données de la structure.

Interruption de l'interprétation

Interruption du flux d'exécution

Les arrêts d'interprétation effectués par certaines AI fonctions ou changement d'état sont émulés par des longjump. Les longjump sont utilisables car nous n'effectuons pas d'allocations devant être libérées absolument via les destructions d'objets déclarés en local (le seul cas qui s'y apparente est le cas des string mais il existe un garde fou qui libère les strings locales restantes en fin de trame). Nous avons essayé d'émuler ce système par des exceptions C++ ce qui est plus propre. Le problème est que la gestion d'une exception, lorsqu'elle se déclenche, est relativement lent. Or de nombreux stop interprétations se produisent à chaque trame : utiliser les exceptions alourdit énormément le temps d'exécution (x5).

Intégration

Interruptions d'interprétation différées

En script les interruptions d'interprétation s'effectuent immédiatement si l'interpréteur traite le code d'un état de base. S'il traite le code d'une fonction, ou le code d'un état exécuté comme callback (via un broadcast message ou un event) l'interruption ne s'effectue pas immédiatement : elle ne se déclenchera que lorsque l'interpréteur retournera dans le code de l'état appelant. Notre système permet d'évaluer si nous nous trouvons dans le code d'une fonction, et selon, traite l'interruption immédiatement où la diffère jusqu'au prochain retour vers le code de l'état appelant (cf.Fonctions scripts 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003700300037003100330036000000 ).

Intégration avec les AI fonctions existantes

Les AI fonctions existantes provoquant des interruptions d'interprétation modifient le flag mb_StopBaseInterpretation de l'interpréteur (lorsqu'un script requiert l'interruption de son interprétation, cette variable passe à vrai). Nous exportons un accès direct en lecture sur ce flag du moteur vers la DLL à l'init (par un pointeur const bool*). Ainsi depuis la DLL nous pouvons savoir à tout moment si une interruption d'interprétation a été requise sans modifier l'implémentation des AI fonctions.

Ces AI fonctions sont caractérisées via M_DefineFunctionEx afin que le prototype correspondant comporte un test provoquant l'interruption de l'exécution lorsque nécessaire. (cf 5.1.3.3 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350032003600370036003900380035000000 ).

Gestion des broadcast messages

Le contexte de longjump est stocké par le Run de l'état de base. Lorsqu'un broadcast message exécute un code, celui-ci est non interruptible. Par contre, le broadcast message peut être appelé par un état de base interruptible : dans ce cas, le broadcast message est susceptible de déclencher une interruption de l'interprétation en sortie. Nous définissons donc les AI fonctions BM_SendMessage avec le flag correspondant (cf 5.1.3.3 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350032003600370036003900380035000000 ).

Gestion des d'états

Interfaçage des états

Dans l'interpréteur de scripts, l'identification des états s'effectue par un index (zero based) dans une table contenant les adresses des nodes de byte code correspondants.

Dans le code C++ nous identifions également les états par cet index.

Dans chaque classe NomModel générée, une table statique de pointeurs sur méthodes est déclarée. Ces pointeurs référencent les méthodes de la classe C++ correspondant aux états. La table d'états référence la totalité des états y compris ceux qui sont hérités. Les méthodes InitState appelées pour chaque classe à l'init de la lib permettent d'initialiser ces tables. Le DLL manager appelle les InitState par couche suivant l'arbre d'héritage des modèles en commençant par les superclasses. De cette manière, la gestion de l'héritage des états s'effectue en copiant préalablement la table de pointeurs de la superclasse avant d'affecter les états surchargés.

Le même principe est appliqué à l'exécution des exit states référencés dans une seconde table.

Exemple

void PCBipedModel::InitState()

A l'exécution l'interpréteur utilise systématiquement la fonction virtuelle Run des AI_GenModelInterface. Cette fonction est chargée (entre autres) de ventiler l'exécution vers l'état courant grâce à l'index d'état passé en paramètre et à la table de pointeurs de méthode de la classe.

void PCBipedModel::Run( int iStateID, bool _bRunExitState,

int16& _iJumpLabelIndex, void* _pInstVar, void* _pMe)

else

else

AI_cl_DLL::ms_oDLL.EndInterpretation(iBackupFunctionOccur);

Changements d'état

Les changements d'état provoquent le plus souvent une interruption de l'interprétation. Pour plus d'informations à ce sujet, consulter le paragraphe précédent (cf Intégration avec les AI fonctions existantes 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003500370031003000340033000000 ).

Il existe plusieurs formes pour changer l'état courant dans les scripts.

Changement d'état à partir d'un état

o       Dans les scripts la forme :

NomDEtat;

.provoque :

l'affectation de l'état comme état courant

l'appel de l'état suivant

l'interruption de l'interprétation

En C++ cela se traduit par :

o       Dans les scripts la forme :

NomDEtat;

stop;

.provoque :

l'affectation de l'état comme état courant

l'interruption de l'interprétation

En C++ cela se traduit par :

F      Afin de se rapprocher de la syntaxe scripts, il est envisagé de placer ces codes de changement d'état dans des fonctions inline générées en début de fichier cpp.

Changement d'état à partir d'une fonction

Les fonctions peuvent changer l'état courant en utilisant les AI fonctions dédiées. Dans ce cas le nom de l'état n'apparaît pas en clair (les AI fonctions utilisent l'index d'état).

Note : ces fonctions peuvent déclencher une requête d'interruption de l'interprétation

Metafonctions

Les metafonctions utilisées dans les scripts ne sont pas simples à émuler en C++ en monothread. Le plus problématique pour l'émulation est de ré-entrer au niveau de la dernière metafonction utilisée dans l'état. Pour ce faire nous utilisons une série de gotos conditionnels générés en entrée de la fonction C++.

Aspects de l'émulation des metafonctions :

Le système utilise les metafonctions existantes de Yeti. Notre méthode d'intégration permet de s'interfacer sur toutes les metafonctions utilisables dans le script. Actuellement seules Wait et WaitForNFrames sont routées dans la lib, mais il est possible d'ajouter facilement les autres formes disponibles dans Yeti.

Une fonction dédiée du DLL engine (Interpretor_PrepareForMetaFunction) permet de préparer le système à l'exécution d'une metafonction moteur.

Un numéro de label de saut identifie la dernière metafonction exécutée dans le code de l'état courant, permettant de réentrer à l'endroit adéquat au prochain réveil de l'AI.

Une série de macros offrent une utilisation synthétique du système proche du code des scripts (l'utilisation de macros est nécessaire pour former les gotos et labels).

L'interruption de l'exécution en cours.

Nous avons simplifié l'interpréteur afin d'unifier le comportement en scripts et C++ : le contexte de métafonctions n'est plus empilé lors d'un appel de fonction dans l'interpréteur. C'est inutile et cela provoque d'étranges comportements en scripts dans de rares cas qui diffèrent du comportement C++.

Exemple de code généré utilisant des metafonctions Wait

void AI_BirdFlock::AI_BirdFlock_Init(int16 &_iJumpLabelIndex)

if( !DYN_bIsReady() )

return;

if( This()->mul_NumberOfBirds == 0 )

}

This()->muc_TypeOfBirdToSpawn = This()->muc_TypeOfBirds;

This()->mul_NumberOfBirdsToSpawn = This()->mul_NumberOfBirds;

// Compute flight direction (One object and one direction per frame)

if( This()->muc_TypeOfBirdsDir== 0 )

}

Static()->GComputeDirectionLocked._ = 0 ;

M_Wait( 1 , 1 );

}

if( This()->muc_ValidFlightDirections== 0 )

}

}

F     La macro M_Wait reçoit en premier paramètre un numéro de label correspondant au numéro de label utilisé dans la macro M_REENTER_FROM_METAFUNCTION. Le(s) paramètre(s) suivant(s) correspond(ent) au(x) paramètre(s) de metafonction habituel(s) que l'on trouve dans le script d'origine.

F     Le switch généré en début de fonction permet de gérer les cas de réentrance après réveil de l'AI suite à l'exécution d'une métafonction. Lorsque l'état s'exécute sans faire suite à un appel de metafonction, _iJumpLabelIndex vaut -1.

F     Dans l'interpréteur, l'index de jump label est stocké dans une variable supplémentaire stockée dans les instances de classe AI_cl_Track.

F     On peut constater que les problèmes de restauration de l'état des variables locales sont similaires à ceux rencontrés dans le script (dans l'exemple, l'utilisation de variables membres pour stocker les compteurs de boucle remédie au problème).

Exécution conditionnelle : Every, EnterState

Les exécutions conditionnelles every et enterstate sont réalisées sous forme d'une macro formant un if et des accolades. Des fonctions moteurs implémentées dans l'API DLL engine permettent de déterminer si l'exécution doit s'effectuer ou non.

#define every(i) if(AI_cl_DLL::ms_oDLL.mp_DLLEngineInterface->b_Interpretor_EveryFrame(i))

#define every_F(f) if(AI_cl_DLL::ms_oDLL.mp_DLLEngineInterface->b_Interpretor_EveryDuration(f))

#define enterState if(AI_cl_DLL::ms_oDLL.mp_DLLEngineInterface->b_Interpretor_EnterState())

Note : afin de gérer l'utilisation des metafonctions au sein des enter state, le code de réentrance des metafonctions est généré avant la gestion de l'enterstate (cf Metafonctions 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003300320036003800350030000000 )

Fonctions scripts

Les fonctions scripts sont traduites sous forme de fonctions membres dans les modèles C++.

  • Les fonctions scripts peuvent retourner plusieurs valeurs de retour en entête. Les paramètres sont en entrée seulement.

int32, int32 Function (int32 _iInputOnly)

En C++, les paramètres de retour supplémentaires sont passées sous forme de pointeurs.

int32 MyModel::Function (int32* _p1, int32 _iInputOnly)

  • Les fonctions scripts ayant un type de retour non void, possèdent toujours un return DEFAULT_VALUE supplémentaire en fin de fonction. En effet, en script il est possible d'écrire du code avec des return manquants sur certains chemins d'exécution (un noud de retour nul est ajouté systématiquement). Ce type de construction ne compile pas en C++. Cet ajout systématique (souvent inutile) de return permet de toujours obtenir du code compilant en C++.
  • Les fonctions scripts retournant un entier sur 8 ou 16 bits, sont traduites en fonctions C++ retournant un entier 32 bits pour contourner un problème d'émulation : en scripts, les valeurs ne sont pas tronquées et sont toujours transmises sur 32 bits.

Exemple

class model_test

object oTest = (model_test*) obj;

uint16 iVal = oTest.Accessor();

C     dans le cas du script, la variable m_VarMembre ne sera pas tronquée et sera copiée entièrement dans iVal. Pour garantir le même comportement en C++, nous retournons toujours la valeur avec la dynamique maximale (32 bits) laissant la responsabilité au code client de tronquer éventuellement la valeur, lors de l'affectation dans la variable de destination.

Contexte courant

De nombreuses AI fonctions se réfèrent à un contexte courant stocké dans l'interpréteur (gao courant, modèle courant, instance courante.).

Il faut donc affecter le contexte courant de l'interpréteur de manière à ce que les AI fonctions se comportent de la même manière lorsqu'elles sont utilisées.

Lorsque l'on exécute un état depuis l'interpréteur, le contexte a été fixé préalablement.

Les cas qui nécessitent un changement du contexte sont :

  • l'appel d'une fonction sur un autre gao model casté
  • l'exécution d'une AI fonction sur un autre objet du type : autre_gao.AIFonction (.)

Fonctions générées depuis les fonctions scripts

La gestion des changements de contexte à l'appel et au retour des fonctions en script s'effectue au début et à la fin de chaque fonction C++ correspondante.

Afin de gérer simplement tous les cas de sortie prématurée pouvant intervenir dans du code via return, nous utilisons la résolution de portée gérée par le compilateur C++. Pour ce faire nous déclarons une instance locale (nommée _) d'une classe spécifique _context_swapper ayant un destructeur chargé de restaurer le contexte en sortie. Le constructeur affecte le contexte courant en entrée. Contructeurs et destructeur basculent le contexte en utilisant des fonctions moteur dédiées via l'API DLLengine.

L'utilisation du swap de contexte dans les fonctions générées depuis les scripts s'écrit sous cette forme :

void MyModel::Fonction ()

Il existe deux constructeurs différents :

  • l'un est dédié aux fonctions non statiques : il assigne le modèle courant et le gao
  • l'autre est dédié aux fonctions statiques : il n'assigne que le modèle courant

Note : la fonction moteur d'affectation du contexte courant pour gao + modèle est plus coûteuse que celle assignant seulement le modèle. En effet elle doit parcourir les tracks du gao pour identifier précisément toutes les informations d'instance nécessaires dans le contexte de l'interpréteur.

Note : l'utilisation du GetType() au lieu de msp_Model à l'appel, permet de remplacer les uc_SearchModelEx par des uc_SearchModel moins couteux lors de cette recherche.

La classe de changement de contexte _context_swapper est également chargée d'émuler les interruptions d'interprétation différées de l'interpréteur de scripts.

En effet, en script, certaines interruptions d'interprétation ne s'effectuent pas immédiatement : elles ne se déclenchent pas tant que l'interpréteur traite le code d'une fonction. De fait, elles se déclenchent au moment où l'interpréteur retourne dans le code de l'état appelant.

Un compteur stocké dans la classe AI_cl_DLL est mis à jour en entrée et en sortie de fonction par la classe _context_swapper. Lorsque ce compteur passe à zéro dans le destructeur ~_context_swapper, cela signifie que l'on revient dans le code d'un état. Dans ce cas, si une interruption de l'interprétation a été requise précédemment, l'exécution du code de l'AI courante est stoppée (cf Interruption de l'interprétation 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003300320037003000330039000000 ).

F     Dans le cas où la fonction ne nécessite pas de changement de contexte mais est susceptible de provoquer l'interruption de l'interprétation en sortie (appel à BM_SendMessage par exemple), une classe n'effectuant que ce travail est utilisée (optimisation) : il s'agit de la classe _stop_interpret_manager.

AI fonctions de Yeti

Comme décrit au paragraphe API des AI fonctions 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003000310039003300370034000000 , le code d'intégration des fonctions AI de Yeti s'exécutant sur un autre objet provoquent le changement / restauration du contexte de gao courant de l'interpréteur.

inline void GFX_ViewportPickable_ (object _obj ,uint8 _par0, bool _par1)

Optimisation

Le swap du context dans une fonction n'est utile que dans le cas où :

  • le code exécuté depuis cette fonction est susceptible de requérir une interruption d'interprétation
  • le code de la fonction courante appelle des AI fonctions qui utilisent le contexte courant de l'interpréteur

Donc dans les fonctions qui :

  1. n'appellent aucune fonction script

et

  1. n'appellent pas d'AI fonction utilisant le contexte de l'interpréteur

. il n'est pas nécessaire de générer de swap de contexte.

F     De fait, il est important de positionner les flags spécifiant quelles AI fonctions n'utilisent pas le contexte de l'interpréteur. De cette manière, le code généré peut gagner en efficacité.

Type object

A l'origine le type object était décrit par une classe object implémentant les opérateurs nécessaires (comparaison, cast.).

Après différentes simplifications, il s'est avéré que la classe object pouvait être remplacée par un simple pointeur. Néanmoins, pour garder un typage fort en C++ nous prémunissant de cast implicites non souhaités dans le code généré, nous avons défini une classe game_object et l'object comme typedef game_object* object.

Par ailleurs la classe game_object mappe le champ name (ou big_file key sur Xbox360) permettant d'identifier les gao facilement dans le debugger.

Type Any

Dans l'interpréteur de scripts il existe un type particulier "Any" qui n'est utilisé qu'avec les AI fonctions d'accès aux tables (TAB_.). Dans un premier temps, de manière à avancer rapidement sans complexifier la génération de sources nous avons créé une classe Any permettant de transtyper tous les types de manière à ce que la lib compile. Ce système fonctionnait mais était relativement risqué : nous avons constaté notamment que certains endroits n'ayant aucun rapport avec Any ne compilaient que par l'intermédiaire de cette classe qui était utilisée implicitement par le compilateur pour transtyper certaines données de manière "contre-nature".

Ce type étant utilisé uniquement pour les fonctions TAB_, nous avons décidé de gérer un cas particulier dans la génération qui transforme le nom de la fonction TAB_... en fonction du type de handle de table adressé.

Par exemple

  • TAB_tGet ( vector* ) devient TAB_tGetVector ( vector* )
  • TAB_Push (int *, int ) devient TAB_PushInt (int*, int)

Les AI fonctions TAB_... correspondantes sont alors définies en "DoNotGenerate" via la macro M_DefineFunctionEx et le routage vers la fonction moteur est implémentée manuellement dans la lib AI.

De cette manière :

  • la classe Any devient inutile, ce qui simplifie le code C++ et évite les cas de transtypage implicite non désirés
  • les fonctions TAB_... C++ sont plus fortement typées ce qui évite toute erreur de type

String et unistring

Strings locales / strings membres

De manière similaire (dans l'idée) à ce que fait l'interpréteur, nous identifions les strings définies en locales des strings membres en fonction de l'adresse this. Si this est une adresse de la stack, la variable est locale. La méthode AI_cl_DLL::bIsStackAddress() permet de déterminer si une adresse fait partie de la stack ou non (c'est une des rares parties du code dont l'implémentation dépend de la plateforme).

Stratégie du string manager pour les scripts

Dans un premier temps nous avons mis en place les classes string et unistring en interfaçant directement les fonctions du string manager via l'interface du DLL engine et en essayant de reproduire le plus fidèlement possible le comportement de l'interpréteur.

Cependant cette méthode engendrait des leaks conduisant rapidement et systématiquement à des remplissages du string manager (nous avons essayé de nombreuses solutions au niveau de la génération C++ pour se rapprocher le plus possible de ce que produit le script, mais en vain.).

La stratégie employée pour les strings par le système de scripts est de transférer la responsabilité de désallocation aux clients potentiels de la string. Le string manager conserve par ailleurs l'adresse du node string script utilisant la string : ce système permet de s'assurer que l'on ne désalloue pas une string actuellement référencée par une variable du code script. Cela permet également d'optimiser certains assignements de strings en changeant le référencement sans copier la string.

Stratégie du string manager pour le code C++

Le transfert de la responsabilité de désallocation aux clients, rend le système sensible et peu robuste aux micro-changements induits par le compilateur C++ par rapport à l'interpréteur.

Après de nombreux essais infructueux nous nous sommes résignés à adapter légèrement la stratégie d'allocation du string manager tout en essayant de modifier le moins de code possible dans l'existant.

L'idée est de se rapprocher de la stratégie habituelle des classes d'encapsulation des strings en C++ (STL notamment) en termes de responsabilité concernant le cycle de vie du buffer : une string est responsable de l'allocation et de la désallocation de son buffer.

Pour ce faire :

Nos types string et unistring possèdent un destructeur chargé de désallouer la string

Nos types string et unistring effectuent une copie systématiquement lors d'une affectation ou concaténation.

Les passages de paramètres avec les AI fonctions s'effectuent par pointeur sans copie. Ces passages s'effectuent par l'intermédiaire des types string_address et unistring_address qui permettent un traitement particulier notamment en sortie d'AI fonction.

Les passages de paramètres dans les fonctions s'effectuent sans copie sous forme const& car en script les paramètres de fonctions sont input only.

Le string manager (AI_cl_StringManager) est enrichi d'un mode d'exécution spécifique AI C++ qui permet de modifier la stratégie de gestion des strings :

o       le système d'identification de la variable référençant une string est ignoré

o       les désallocations en provenance des AI fonctions sont ignorées

o       les désallocations en provenance du code C++ sont toujours traitées

o       un service supplémentaire permet de flager une string après création comme étant locale ou membre. Cela est utile pour assigner par pointeur dans son instance C++, une string allouée au sein d'une AI fonction (ce n'est qu'à l'affectation dans la variable que l'on peut savoir si elle est locale ou non).

Le système de gestion des tables (AI_cl_Table) est enrichi d'un mode d'exécution spécifique AI C++ qui permet de modifier la stratégie de gestion des tableaux de strings et d'unistrings :

o       de même que pour un vector de string STL, les strings sont copiées lors de leur affectation dans le tableau.

o       lors d'un remove ou d'un empty du tableau, les strings sont libérées

o       lors d'un décalage des strings dans le tableau, il n'est pas nécessaire de mettre à jour le système de référencement du string manager puisque celui-ci est ignoré en mode AI C++

F     dans le cas d'un Get ou d'un Top la string est copiée, dans le cas d'un Pop elle est passée par pointeur puis flagée comme locale ou non (optimisation) => voir code des AI fonctions correspondantes dans AI_Prototypes.h

F     Le cas de strings membres de structs utilisées dans un tableau dynamique de structs n'est pas géré correctement par le système. Cependant les effets sont plus critiques en mode C++ qu'en mode script. Il ne faut pas utiliser de telles constructions. Un warning est affiché par le compilateur de scripts dans ces cas.

Cohabitation avec les interruptions d'interprétation

Le système d'interruption de l'interprétation repose sur des fonctions C longjump et non sur le mécanisme d'exception C++ pour des raisons de performances (cf Interruption de l'interprétation 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350030003300320037003000330039000000 ). De fait il est possible que des destructeurs de strings locales ne soient pas exécutés lorsque cela se produit. Fort heureusement le système mis en place pour les scripts purge le string manager de toutes les strings locales restantes en fin de trame. Notre système mettant à jour l'information "locale ou membre" dans le string manager, ces strings "oubliées" seront purgées en fin de trame.

Bool array

Dans l'interpréteur de scripts, les tableaux statiques de booléens utilisent un codage par bit et non par octet comme le fait C++.

La classe template AI_BoolArray permet d'obtenir des tableaux de booléens identiques en termes d'utilisation et de mapping mémoire bit à bit à ceux gérés par l'interpréteur. Seule la déclaration du tableau diffère légèrement du script.

Ce système convient également à l'émulation des tableaux de booléens à deux dimensions (tableau statique de tableaux de booléens bit à bit).

Maths classes

Les scripts proposent deux types mathématiques natifs : le vector 3d et la matrice 4x4. L'émulation dans la DLL s'effectue via deux classes vector et matrix présentant le même mapping mémoire et implémentant les opérateurs de base (+, -, *, /, =, ==, !=).

Les vector et matrix possèdent des constructeurs avec ou sans init des valeurs par défaut.

Pour optimiser le passage de paramètres dans les AI fonctions, les vector et matrix sont transmis par référence. Le problème est qu'un tel typage ne compile pas dans tous les cas sous gcc qui refuse de passer une référence non const sur une instance temporaire, ce qui est le cas lorsque l'instance émane d'un retour de fonction par exemple. Dans les adapteurs générés pour les AI fonctions, les paramètres sont donc définis en const vector& et const matrix&, le specifier const est ensuite "annulé" par cast.

Références sur ressources moteur

Dans les scripts Yeti il est possible de référencer des ressources moteur de différents types directement par leur nom. Ces références sont exploitées via des AI fonctions dédiées à l'utilisation de chaque type.

La plupart de ces références sont traitées par les AI fonctions comme l'adresse d'un gao. D'autres (gui et world) sont reconnues comme des clés de fichiers de big file.

Dans le code C++, à chaque référence identifiée dans le code script :

  • Pour les gui et world nous générons dans le fichier header AI_GenReferences.h des constantes de type uint32 nommées comme les ressources et dont les valeurs sont les clés de big file correspondantes. Le code client C++ généré utilise ces constantes directement.
  • Pour les autres types de références nous générons dans le fichier header AI_GenReferences.h des constantes de type uint32 nommées comme les ressources et dont les valeurs sont des index dans les tables globales AI_ga_Ref et AI_ga_RefKey. Dans la table AI_ga_RefKey, la clé de fichier big file correspondant à une ressource est générée à cet index. A l'init de lib, l'adresse du gao est assignée à l'index correspondant dans la table AI_ga_Ref grace à un service de recherche de l'adresse à partir de la clé de big file. Le code client C++ généré utilise le tableau de références et la constante pour spécifier la ressource moteur : AI_ga_Ref [NomDeLaRessource]

Modifications de l'interpréteur

  • nous avons modifié l'alignement des variables d'instance dans l'interpréteur dans certains cas d'héritage pour obtenir un mapping compatible avec le C++ (cf. Variables d'instance 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350035003600300038003100350030000000 )
  • nous avons modifié l'interpréteur pour rendre son fonctionnement conforme au C++ dans un cas où l'effet produit en script n'était pas intuitif. Lors de l'appel d'une function le numéro de métafonction était empilé par l'interpréteur. Cela provoquait un comportement différent en C++ et en scripts dans le cas suivant :

Une track T d'un gao G est en attente sur une métafonction

Un autre objet appelle une fonction sur G qui change l'état de T avec un SetNextState : cette opération annule l'état d'attente sur métafonction de T.

En sortant de la fonction script le contexte de métafonction est restauré par l'interpréteur => la track T de G est de nouveau positionnée en attente sur la métafonction. Or ce n'est pas le cas en C++.

F     Ce fonctionnement n'est pas intuitif pour les Gpps et l'empilement du numéro de métafonction courante n'est pas réellement utile : nous l'avons donc supprimé de l'interpréteur. De cette manière nous obtenons un comportement équivalent en scripts et en C++.

Divers

  • Les basic types uint8, int8, uint16, int16, uint32, int32 de Yeti sont répliqués dans le precompiled header de la DLL formant ainsi une base commune.
  • Les types de handles de table dynamique sont définis dans le precompiled header sous la forme d'un typedef void* type_p;

Exemple int_p, curve_p, string_p

Note : les handles de table renvoyés par les AI fonctions ne sont pas des pointeurs

  • Le code de la lib dispose d'asserts non compilés en release (contrairement aux scripts) déclenchant un assert dans l'éditeur : AI_ASSERT, AI_ASSERT_MSG
  • Le code de la lib dispose d'assert "compile time" en debug : AI_COMPILE_ASSERT
  • L'interpréteur de scripts autorise des caractères qui ne sont pas autorisés en C++ dans les noms de modèles, de méthodes, de variables... Les noms peuvent commencer par un chiffre, ils peuvent contenir des caractères interdits comme le $ par exemple. En C++, nous insérons ou remplaçons certains caractères par '_' de manière à obtenir des équivalents proches qui compilent.
  • l'opérateur script '²' est traduit en fonction Square (.)
  • l'opérateur script '^^' est transcris en fonction Power (.)

gcc

Comme mentionné dans les paragraphes précédents, gcc est moins tolérant sur la syntaxe C++. Dans différents cas, nous avons été obligés de modifier la génération et les scripts originaux pour contourner ce problème.

Cependant gcc nous pose des problèmes d'une autre nature : il supporte mal la montée en charge contrairement à Visual. Il produit notamment des fichiers beaucoup plus gros que Visual pour un même travail (precompiled header gch de 160 Mo contre 28 Mo pour le pch, lib debug de 350 mo contre 70 mo.) et se trouve ensuite incapable de les gérer correctement.

Precompiled header .gch

Nous avons du optimiser la liste des includes que nous plaçons dans le precompiled header. A l'origine le gch pesait 160 Mo, mais gcc était incapable de l'utiliser lors de la compilation des fichiers .cpp (message d'erreur "had to rellocate pch"). La compilation avec precompiled header est de l'ordre de 15 min, sans le gch le temps s'envole à environ 2 heures ce qui n'est pas envisageable d'un point de vue pratique.

Dans un premier temps, nous avons eu l'idée de répliquer le modèle d'includes des scripts disponibles dans les infos de pre-processing du compilateur de scripts : les includes déplacés dans leurs .cpp clients ne figurent plus dans le precompiled header... Malheureusement, nous avons rencontré des problèmes d'inclusions cycliques insolubles, qui ne semblent pas poser problème en script, mais qui ne compilent pas en C++.

Dans un second temps, nous avons décidé d'appliquer ce principe uniquement sur les fichiers générés par le pattern editor (fichiers script dont le nom contient un $) : en effet le code généré par le pattern editor est plus simple, systématique et n'engendre pas ces problèmes d'includes cycliques. Les AI écrites par les GpP restent incluses via le precompiled header. Cette optimisation a permis de réduire la taille du precompiled header à 97Mo, taille que gcc est capable d'exploiter lors de la compilation (la limite est à 128Mo).

Compilation

Les instanciations statiques des classes modèles sont désormais réparties dans chaque fichier .cpp généré des AI_GenModel. A l'origine, elles étaient concentrées dans AI_GenDllEntry.cpp. Avec les optimisations activées (release), gcc bloquait sur la compilation de ce fichier (boucle infinie ?).

D'autre part il n'est pas possible d'utiliser le mode de génération "tout dans un seul metafile" : contrairement à Visual, gcc est incapable de compiler le fichier généré de 650000 lignes et affiche un message de dépassement.

Link

Lorsque les informations de debug sont activées, la lib AI complète (pour tout le big file) pèse plus de 300 Mo. gcc est incapable de linker correctement une telle lib avec le corps du moteur. Il plante au link après un certain temps avec un message indiquant un dépassement de capacité dans les tables de link ou un assert...

Pour contourner ce problème une solution est d'appliquer l'option de compilation

-mminimal-toc sur toutes les libs du projet (y compris l'AI lib) en debug.

Cette option réduit potentiellement l'efficacité du code généré mais cela n'est pas gênant puisque cela concerne uniquement la target Debug.

Options de compilation

Les méthodes de passage de paramètres vers les AI fonctions de Yeti depuis le code script ne sont pas très rigoureuses. Le code C++ généré étant calqué sur le système scripts nous rencontrons des problèmes similaires. Pour que le code fonctionne en niveau d'optimisation -O2, il faut ajouter l'option -fno-strictaliasing (cette option a également été utilisée sur l'aiengine pour le mode interprété). Cette option désactive une optimisation incompatible avec les passages de paramètres non rigoureux.

Comme décrit précédemment (cf Problèmes connus de syntaxe 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350031003300350032003400380032000000 ) nous avons activé l'option -fno-for-scope permettant d'obtenir une portée de variable dans les boucles for identiques aux scripts

PRX

La technologie fournie par Sony pour charger des modules de code dynamiquement est le système de PRX. Malheureusement ce système est beaucoup plus spécifique et restrictif que le système de DLL de Microsoft.

L'adressage du code ou des données stockées dans le PRX depuis l'elf est un système spécifique implémenté à la compilation qui translate les adresses. En conséquence il n'est pas possible d'appeler des fonctions du PRX via un pointeur de fonction, ni d'adresser une donnée statique du PRX via un pointeur.

Dans le cas de la lib nous utilisons des interfaces abstraites "COM like". Dans le cas du PRX il n'est pas possible d'appeler directement une méthode virtuelle de la lib via un pointeur d'objet depuis le moteur vers la lib (le contraire fonctionne). L'utilisation de PRX pour la lib AI implique de créer des fonctions wrappers au sein du PRX pour chaque méthode virtuelle appelable depuis le moteur. Ces wrappers effectuent réellement l'appel de méthode avec le pointeur d'objet passé en paramètre. Cela signifie que les appels de fonction du moteur vers la lib sont transformés en double appel.

F     Ces contraintes nous ont amené à envisager l'utilisation du link statique sur PS3 (plus simple, plus performant et portable). Si l'on veut permettre aux équipes de production de construire l'elf avec une lib AI sans avoir les sources du moteur, il est envisageable de leur livrer les fichier lib des modules moteur. Un fichier projet spécifique leur permettra de linker ces lib et construire l'elf exécutable.

PC / Xbox360

Moteur

AI_cl_GenModelInterface* poGenModel =

m_pDLLInterface->GetGenModel (gpo_BigFile->ul_GetFileKey(ul_FileIndex));

poModel->vSetGenModel ( poGenModel );

if ( poGenModel != NULL )

DLL

class MyModel : AI_GenModel

MyModel::AssignStaticData (void* _pAdr, void* _pModel)

PS3

Moteur

AI_cl_GenModelInterface* poGenModel =

AI_DLLInterface_GetGenModel

(m_pDLLInterface, gpo_BigFile->ul_GetFileKey(ul_FileIndex) );

poModel->vSetGenModel ( poGenModel );

if ( poGenModel != NULL )

PRX

// PRX declaration

SYS_MODULE_INFO( AIDLL_Module, 0, 1, 0 );

SYS_MODULE_START( _start );

SYS_MODULE_STOP( _stop );

int _start(void);

int _stop(void);

int _start(void)

int _stop(void)

SYS_LIB_DECLARE(AIDLL, SYS_LIB_AUTO_EXPORT | SYS_LIB_WEAK_IMPORT);

// Wrappers

SYS_LIB_EXPORT(AI_DLLInterface_GetGenModel, AIDLL );

AI_cl_GenModelInterface* AI_DLLInterface_GetGenModel

(void* _pDLL, uint32 _uiKey)

SYS_LIB_EXPORT(AssignStaticData, AIDLL );

void AssignStaticData (void* _pGenModel, void* _pAdr, void* _pModel)

// Code

class MyModel : AI_GenModel

MyModel::AssignStaticData (void* _pAdr, void* _pModel)

Profiling

Outils

Utilisation du profiler pour l'AI sur Xbox360

  1. Dans ENGine\UNIverse\UNI_EngineLoop.cpp UNI_EngineLoop::poRunOneFrame.cpp mettre un breakpoint :

  1. Lancer, une fois breaké, changer dans la fenêtre watch de Visual la valeur de mo_Trace.mi_Context avec une valeur de l'enum de UNI_EngineLoop.h :

  1. Enlever le breakpoint, appuyer F5. Un fichier trace.bin est alors généré sur la Xbox (l'exécution de la trame en est fortement ralenti).
  1. Copier ce fichier dans le répertoire \exe de yeti sur votre PC.
  1. Lancer le fichier trace.bat, il va générer un fichier trace.cap
  1. Ouvrir le programme xbPerfView (Démarrer>Programmes>Microsoft Xbox 360 SDK>¨Performances>Call-Attributed Profile Viewer (xbPerfView))
  1. Ouvrir > Raw CAP files (*.cap) sélectionner le fichier généré par trace.bat

Utilisation de Code Analyst sur PC

Avec CodeAnalyst l'échantillonnage est beaucoup plus précis sur une machine ayant un processeur AMD. Sur Intel la période minimale est limitée à 1ms.

  1. Fixer les options de projet  comme suit :

  1. Appuyer sur play pour commencer la session
  2. Appuyer assez rapidement sur pause afin de suspendre l'échantillonnage tant que vous n'êtes pas dans la partie de l'application à bencher
  3. Placer l'application dans la situation à bencher
  4. Ré-appuyer sur pause pour lancer l'échantillonage
  5. Laisser tourner un certain temps (sur Intel, il faut laisser tourner quelques minutes puisque l'échantillonnage s'effectue à 1ms).
  6. Appuyer sur Stop (l'application se ferme)
  7. Dans la liste, double cliquer sur la DLL AI. La description des samples en recoupant avec le fichier .pdb (il est important que celui-ci corresponde à la DLL utilisée).

  1. Le résultat apparaît, Avec la DLL Debug il y a plus de détails (toutes les fonctions apparaissent puisqu'il n'y pas d'inline) mais les performances sont assez différentes de la version Release.

Outils de profiling intégrés

Le système de rasters spécifiques développés sur Graw pour l'AI script a été adapté pour fonctionner avec l'AI C++.

Le système s'active via les fichiers SDK\RASter\RAS_SpecRaster.h et SDK\RASter\RAS_SpecRaster.cpp.

La compilation de ces 2 fichiers doit être activée dans le vcproj, car ils sont exclus du build par défaut.

Dans SDK\RASter\RAS_SpecRaster.h il faut :

Définir le flag #define USE_SPEC_RASTER

Dans SDK\RASter\RAS_SpecRaster.cpp il faut :

Activer le raster spécifique "Callstack" :

const bool C_abManagedFamily[ERasterFamily_Number] =

Définir un des deux flags (exclusivement) #define FIND_SLOW_CALLSTACK ou

#define ANALYZE_CALLSTACK en fonction du mode de raster désiré.

FIND_SLOW_CALLSTACK liste la liste d'états de base exécutés sur une trame avec le temps d'exécution correspondant

ANALYZE_CALLSTACK liste la callstack complète avec les temps d'exécution pour chaque fonction à partir de l'état de base spécifié par la constante const char * C_szScriptToAnalyze = "Track 0x3407a8da State 1"; (la valeur hexa est la clé big file du model à analyser)

Le système dump toutes les 2 secondes l'ensemble de deux traces dans le log :

une trace correspondant à la trame AI la plus longue détectée sur les 2 secondes

une trace correspondant à la somme des temps d'exécution sur les 2 secondes. Dans le cas du mode callstack, le système identifie par callstacks identiques.

F     En AI C++, en raster mode callstack, les index de fonctions ne sont pas mis à jour. La trace ne spécifie que le modèle contenant la fonction.

Statistiques

Voici quelques statistiques actuelles sur l'AI de la version officielle de Graw 2. Les libs utilisées pour ces tests ont été construites en incluant les AIs patterns pour toutes les maps officielles.

Performance

Nous obtenons un gain d'environ 50% sur le raster global de l'AI. Une statistique effectuée en début de projet conférait 60% du temps global AI à l'interprétation (40% aux AI fonctions), cela signifie que nous obtenons 600% de gain en performance sur l'interprétation. Cette estimation est corroborée par des mesures récentes : en DLL non FINAL nous disposons désormais d'un raster séparé pour l'interprétation et les AI fonctions.

Taille des libs

Taille des fichiers :

taille des .dll (ou .xex) release sans infos de debug

différence sur la taille de l'elf final strippé dans le cas du link statique

PC dll

Xbox360 dll

PS3 lib (delta elf strippé)

favor small code

4480 ko

5224 ko

21.8Mb - 14.5Mb = 7.3Mb

maximize speed

6384 ko

6600 ko

31Mb - 14.5Mb = 16.5Mb

maximise speed (-O3)

X

X

31Mb - 14.5Mb = 16.5Mb

favor small code + LTCG

4532 ko

5192 ko

X

maximize speed + LTCG

6444 ko

5448 ko

X

F     ne pas charger le byte code script d'une map économise environ 3Mb

F     le code AI est chargé d'un seul bloc et non en deux DLLs partie fixe / partie map car il y a des dépendances qu'il faudrait casser dans les classes de base des patterns

Temps de compilation

Quelques temps de compilation sur un Bi Xeon 3Ghz / 3Gb de RAM.

Target

Compilation

PC dll

Xbox360 dll

PS3 lib

release avec infos de debug

Locale

Incredibuild

6 min 30

2 min 39

3 min 29

release sans infos de debug

Locale

Incredibuild

2 min 05

7 min 40

X

debug

Locale

Incredibuild

3 min 20

3 min 43

finale (LTCG)

Locale

Incredibuild

5 min 36

7 min 35

Evolutions

Court terme : avec le système actuel de génération

Dans le système actuel, l'effort de développement s'est focalisé sur la fidélité de l'émulation des scripts par la lib C++. Néanmoins, il est possible d'améliorer le système sur d'autres points.

Confort d'utilisation

En utilisant le mécanisme d'exception handler du système win32, il est peut-être possible d'effectuer des récupérations sur erreur lorsqu'un plantage intervient dans le code de l'AI lib. Tant qu'il n'y a pas d'écrasement mémoire irrémédiable, l'erreur peut-être récupérée sans engendrer l'arrêt de l'éditeur. Une fois l'erreur détectée par le système il suffit de stopper le jeu, puis sauter à un endroit sécurisé de la boucle moteur (longjump ?)

En adaptant les scripts, il sera possible d'activer le système d'intégration des macros (simples) dans le code généré : dans les scripts il faut supprimer les cas de macros portant le même nom et ayant des valeurs différentes, il faut éviter les déclarations de macros au sein des fonctions.

Il est possible d'automatiser (via OLE ?) Visual depuis l'éditeur d'AI Yeti. Cela permettrait de commander Visual depuis l'éditeur d'AI afin de rendre le travail des Gpps plus confortable. Voici quelques possibilités :

lancer Visual et ouvrir le projet AIDLL automatiquement

passer Visual en avant plan

ouvrir les fichiers .h et .cpp correspondant à un modèle sélectionné dans l'éditeur dans Visual

déclencher les build / rebuild depuis l'éditeur

lancer et connecter le debugger sur la DLL

placer un breakpoint automatiquement sur la méthode AI_cl_DLL::AICheckCallback

Optimisations

Il est également possible de pousser plus loin les différentes pistes d'optimisations :

Continuer à mettre à jour les flags des AI fonctions n'utilisant pas le context pour les nouvelles AI fonctions qui sont développées

Ré-implémenter en inline dans la lib toute les AI fonctions simples pour lesquelles l'appel est couteux relativement au contenu de la fonction (fonctions math notamment type sqrt.).

Passer les matrix et éventuellement vector en entrée de fonction en const& au lieu de les passer par valeur. Cela requiert des adaptations dans les scripts modifiant la valeur des paramètres entrants.

Optimisations bas niveau en améliorant le code des fonctions (memcpy.)

Eviter la multiplication des appels de fonctions intermédiaires pour positionner le contexte : il est envisageable de stocker, à l'init de la DLL, des pointeurs sur les variables de contexte de l'interpréteur pour pouvoir les modifier sans appels fonctionnels.

Mieux optimiser les changements de contexte : pour ce faire la génération pourrait s'effectuer en deux passes. Une première passe de compilation script servirait à construire le graphe d'appel des fonctions script. La deuxième passe qui effectuerait la génération du code (comme actuellement) disposerait du graphe d'appel pour optimiser plus efficacement les changements de contexte. En effet, il est inutile de générer un changement de contexte dans une fonction qui n'utilise que des fonctions qui, elles-même, n'utilisent pas le contexte.

Actuellement l'identification des modèles utiles pour la version par rapport aux modèles de test s'effectue seulement pour les scripts de pattern par sélection des répertoires correspondant aux maps officielles (excepté les répertoires spécifiés dans le fichier editor.ini). Il serait intéressant d'ajouter une propriété au modèle par un mot clé dans le fichier .vas permettant de spécifier que la classe ne figure pas dans le game data de la version finale. Cela permettrait d'optimiser la taille du code de la lib lorsque les sources sont générés par l'option "generate sources - official maps".

Long terme : développement Gameplay programming en C++

Le travail actuel en C++ sous forme de génération automatique en post build permet de migrer en souplesse vers un système C++. Par contre il rallonge considérablement le process et impose une partie du travail en double.

Actuellement mise au point scripts

puis génération / compilation C++

Avec un système Gpp C++

1. modifications scripts compilation scripts

2. génération des sources C++

3. compilation de la DLL

1. modifications C++ compilation DLL

A une étape clé (changement de projet), il est envisageable de migrer sur un mode de développement Gameplay programming purement C++. Dans cette optique il est très important de garder le meilleur des deux mondes scripts et C++, afin de conserver un mode de développement réactif et souple permettant des cycles développement / test courts.

Avec cette méthode de travail, les Gpps écrivent le code en C++ et archivent sur une base perforce dédiée.

Partant de la version actuelle de Yeti, voici les évolutions techniques possibles pour y parvenir en tirant parti de l'existant.

Editeur et réflexivité pour les classes AI

Le fait que les structures de données (variables d'instance, variables de modèle) soient connues et gérées par l'éditeur présente plusieurs avantages :

Les données métiers peuvent être éditées simplement via l'éditeur (notamment par les Level Designers)

Le code AI ne maintient pas lui-même les données :

o       cela facilite le rebuild et reload à chaud de la DLL. Actuellement la DLL est unloaded dès que le jeu est mis en pause, ce qui permet de la re-builder sans redémarrer la map

o       cela évite d'effectuer les allocations mémoires côté DLL, et/ou d'avoir à reconstruire les objets. Le code C++ de la DLL est responsable des comportements et des données temporaires allouées sur la pile mais la gestion de l'état courant de l'objet (toutes les données persistantes de l'objet) et la reinit de ces données restent de la responsabilité du moteur

En termes de compatibilité avec l'existant :

o       cela permet de conserver la granularité des AI instances par rapport aux GAO, le système de tracks actuel.

F     Une solution qui évite de tout re-développer :

  1. il faut conserver la déclaration des variables d'instance, des variables de modèle, des fonctions et des états côté éditeur.
  2. par contre le code ne sera plus édité dans l'éditeur. Actuellement chaque fonction ou état correspond à un fichier du big file. Ceux-ci ne contiendront que la déclaration de prototype : il serait peut-être mieux de décaler la déclaration dans le fichier .vas pour éviter de conserver de nombreux fichiers inutiles.
  3. la génération se résume à générer / updater les squelettes de fichier C++ pour chaque modèle avec les variables de classes et les fonctions correspondantes à la déclaration du modèle. Un système de gardes permettra de conserver le code édité au sein du squelette généré. Ce système pourrait grandement s'inspirer du système de génération de code C++ de Rational Rose (Rose cpp). En effet, dans Rose, la répartition des responsabilités génération des squelettes / écriture du code est similaire. Les services structurels assurant la liaison avec le moteur seront générés également par ce biais (assignation des variables statiques, vérification d'alignement, le Run ventilant l'exécution vers les états).

Afin de faire cohabiter code généré et code écrit manuellement il serait souhaitable d'ajouter un mot clé dans les fichiers .vas pour déclarer si le modèle correspondant est écrit directement en C++ ou en script.

F     On peut noter que de nombreux moteurs de jeu (notamment ceux orientés édition) possèdent un système de réflexivité des données permettant à l'éditeur et parfois même au moteur de manipuler de manière générique les données métier :

o       Unreal possède un système de réflexivité avec les fichiers .uc. Les classes C++ sont générées à partir des fichiers .uc.

o       Renderware permet d'exporter des variables pour l'édition qui sont taggées dans le code C++.

o       Dans les autres moteurs d'Ubi : GDS possède un système de réflexivité pour l'édition des données dans les plug-ins Max (l'expose system), Assassin possède également un système de réflexivité (fichiers descriptifs object models) .

Références sur data

Actuellement la table de références sur data est générée de manière globale en considérant la liste de toutes les data spécifiées sous forme texte (noms de ressource) dans le code script et résolue à la compilation script. Cette liste est toujours optimale puisqu'elle contient la liste des références utilisées. En effet il n'est pas possible de générer par défaut une table de références pour toute les data du jeu.

Sans compilation script, pour garder une efficacité comparable, il faut ajouter un mécanisme spécifique pour l'import de données. Dans la solution décrite dans le paragraphe précédent, les fichiers .vas continueront à être utilisés pour déclarer les classes. Le plus simple serait d'ajouter une syntaxe spécifique dans ces fichiers pour importer une donnée.

Exemple

import NomDeRessource;

Actuellement les références sont générées dans un fichier .h global inclus dans le header precompilé stdafx.h. Conserver cette architecture physique signifie qu'ajouter un import de donnée provoquerait la recompilation complète de la DLL.

L'autre solution est de générer les références uniquement dans des fichiers .cpp :

générer les macros définissant les index de ressource ou clés de big file (selon le type de donnée, cf. Références sur ressources moteur 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200300036003700360038000000 ) en début de fichier .cpp de GenModel correspondant au .vas contenant les mots clé import.

pour les références de type object, garder la table de correspondance index / adresse gao dans le fichier AI_GenDllEntry.cpp

F     pour que cette stratégie fonctionne il faut que les références existantes restent à des index stables dans la table en cas de suppression / ajout afin d'éviter de modifier de nombreux gen model. Pour ce faire il faut conserver l'état de cette table de manière persistante dans l'éditeur et lui appliquer une gestion à "trous". La table générée dans AI_GenDllEntry.cpp sera le reflet de cette table stockée côté éditeur.

Amélioration de syntaxe par rapport au code généré actuel

Actuellement la syntaxe du code généré de l'AI C++ est conçue pour faciliter la génération depuis le code script et minimiser les modifications dans l'interpréteur. Dans le cas où les gameplay programmers travaillent directement en C++, il est possible de concevoir une syntaxe plus adaptée à l'écriture manuelle.

Unification des classes behaviour et data

Dans l'idée de développer directement en C++, un élément qui simplifierait grandement la syntaxe serait d'unifier les classes GenModel et GenModel_Data. Les changements techniques que cela implique sont :

  • le mapping de la classe C++ sur les espaces gérés par l'interpréteur doit être conservé : dans la construction de l'espace de variable il faut réserver 8 octets en début de buffer pour les superclasses. Ces 8 octets mis à jour à la création de l'objet accueilleront :
    • le pointeur de virtual table de la classe C++
    • un pointeur sur le gao contenant la track contenant l'instance. Le membre me de la classe C++ sera assigné à la construction de l'objet par le moteur.
  • les instances statiques de modèle déclarées dans la DLL seront remplacées par un système de factory permettant de construire l'instance d'un modèle à une adresse donnée (new placement). Ce service sera utilisé par le moteur pour créer la partie C++ de chaque nouvelle instance d'objet AI (y compris pour les constructions dynamiques => il faudra vérifier la compatibilité de ce système avec les classes string et procéder aux aménagements nécessaires, le cas échéant). Ce système est d'ores et déjà prototypé et fonctionnel dans l'AIDLL et l'interface dans les zones gardées par #ifdef FACTORY
  • les variables déclarées actuellement dans les classes Data seront déclarées directement dans les classes Model. Le système d'indirection via référence pour les variables de modèle devra être conservé. Il est possible de les remplacer par des pointeurs, ce qui serait plus compréhensible pour du code écrit à la main. Ces variables seront déclarées statiques.

  • les structs doivent conserver un mapping mémoire rigoureux sans le pointeur de vftable (et le me) intégré dans les classes modèles. Cela implique de différencier clairement modèles et structures au niveau de la déclaration dans l'éditeur afin de générer soit un modèle, soit une structure (sans pointeur de vtable) : les structures ne posséderont pas de méthode virtuelle (=> pas d'état). Pour ce faire il faudrait ajouter un mot clé spécifique dans le fichier .vas. Exemple : struct
  • le model_cast serait plus simple (et plus performant) que le système actuel de model_instance puisqu'il ne serait plus nécessaire de stocker le me, instance var et pvftable dans l'espace de variable local. On peut envisager une fonction globale template ressemblant au dynamic_cast du C++. En entrée, le développeur fournit une adresse de gao et un type destination. La fonction renvoie un pointeur sur le modèle correspondant.

template<class TModel> TModel* model_cast<TModel> (object _pGao)

else

}

return pGenModel;

L'utilisation prendrait la forme :

MyModel* poModel = model_cast<MyModel> (me);

poModel->MyFunction ();

X = poModel->MyVariable;

Changements de contexte dans les fonctions

Méthode 1 : conserver les changements de contexte

Une première méthode est de rester proche du système existant : elle consiste à générer le changement de contexte en même temps que le prototype de fonction. Cela est tout à fait utilisable, mais empêche d'optimiser les changements de contexte en supprimant ceux qui sont inutiles (sinon il faudrait re-parser le contenu de la fonction à un moment pour générer correctement son header). Si les changements de contexte sont systématiques, il faudra travailler sur l'optimisation de leur implémentation en permettant d'accéder directement aux éléments à modifier dans l'interpréteur depuis la lib (pour éviter les appels fonctionnels).

Méthode 2 : supprimer les changements de contexte

Si l'on souhaite alléger la notation et la charge d'exécution en supprimant l'affectation du contexte courant au sein de l'interpréteur, il faut s'intéresser :

  • à la manière dont les paramètres vont être passés au sein des AI fonctions. En effet l'affectation de contexte en C++ n'est utile que pour les appels aux AI fonctions qui utilisent des valeurs globales de contexte stockées dans l'interpréteur. Ce qui me semble envisageable est d'utiliser le système de flags actuel des AI pour connaître la nature du contexte qu'elles utilisent. Selon le ce qui est nécessaire pour la fonction, le système générera un prototype d'AI fonction prenant en paramètre le contexte nécessaire :
    • aucun paramètre supplémentaire pour les AI fonctions n'utilisant aucun contexte
    • un pointeur sur le gao (type object) pour les AI fonctions utilisant le gao sans le modèle. Pour exécuter sur le modèle courant, transmettre me
    • un pointeur sur le modèle (AI_GenModel*) pour les AI fonctions nécessitant le gao et le modèle. Pour exécuter sur le modèle courant, transmettre this. Dans ce cas le numéro de la track doit être disponible dans l'objet : il faudrait le placer dans l'espace d'instance. Dans ce cas il faudrait interdire la possibilité de partager les variables d'instance entre deux instances d'une même classe placées sur deux tracks différentes d'un même gao.

F     Les AI fonctions utilisant le contexte étant minoritaires cela semble être une orientation intéressante : c'est la solution la plus propre, la plus évolutive, et économique en termes de performance en permettant la suppression des changements de contexte.

F     Pour les fonctions AI utilisant le contexte, il est possible l'alléger la syntaxe en générant le wrapper inline comme méthode de la classe AI_GenModel (au lieu d'une fonction globale). De cette manière la méthode s'appliquerait par défaut sur l'instance de modèle courant correspondant au this. Pour appliquer la méthode sur un autre objet il suffirait d'utiliser la syntaxe C++ object.AIFonction.

Proposition

Les wrappers d'AI fonctions n'utilisant pas le contexte sont générés comme actuellement dans AI_GenPrototypes.h

Les wrappers d'AI fonctions utilisant le contexte sont générés dans un fichier AI_GenPrototypesWithContext.h

Dans le fichier AI_GenModel.h, dans la déclaration de classe AI_GenModel les wrappers sont inclus sous forme de méthode :

class AI_GenModel : public AI_cl_GenModelInterface

bool operator != (const AI_GenModel& _Obj)

operator object() const

object* operator& ()

virtual void* GetType() const

virtual void Run( int iStateID, bool _bRunExitState,

int16& _iJumpLabelIndex, void* _pInstVar,

void* _pMe, bool _bInteruptMode)

virtual AI_GenModel* Clone (void* _pPlacementAdr,

void* _pInstanceVar, object _me) = 0;

#include "AI_GenPrototypesWithContext.h"

};

  • aux interruptions d'interprétation qui n'affectent pas le déroulement des fonctions mais seulement celui de l'état de base :
    • est-il envisageable de faire un autre choix en changeant la façon dont le gameplay programmer interagit avec le système ? (interrompre toujours tout de suite ou ne pas interrompre ?)

F     par ailleurs, on peut craindre que l'utilisation des long jumps soit bloquante pour l'évolution future du système (impossibilité de compter sur l'exécution des destructeurs des variables locales)

    • générer deux méthodes pour chaque fonction : un wrapper inline et une fonction contenant le code des gpps comme suit :

class MyModel

.

avec

class AI_cl_DLL

void LeaveFunction()

}

}

.

String

Le string manager pose des problèmes de désallocation dans un certain nombre de cas en script. Sa stratégie le rend sensible à l'utilisation des chaînes dans le script : il faut bien respecter un certain nombre de guidelines pour éviter les problèmes de désallocation ou d'écrasement de chaines. De ce fait, il y a une large demande des équipes dev et gpp pour un re-factor du string manager résolvant ces problèmes.

En C++ la stratégie le rend un peu plus robuste au niveau des désallocations cependant il y a des limites au système. Voici quelques idées concernant l'évolution du système :

le système ne gère pas correctement les tableaux dynamiques de structs contenant des strings. De manière générale, les structs sont passées sous forme non typées aux AI fonctions (adresse + taille). Donc une fois transmis dans l'AI fonction, aucun constructeur ou destructeur correspondant à un membre de la struct pour ne pourra être exécuté automatiquement par le langage. Si l'on veut résoudre ce problème pour du code C++ écrit à la main, je pense qu'il faudrait remonter le concept de container au niveau de la lib (une classe template vector par exemple) : la gestion du container s'effectuerait côté lib, les opérations d'allocations resteraient côté moteur. De cette manière les copies, remove d'éléments. seraient effectués côté C++ et pourraient ainsi appeler les constructeurs et destructeur des membres de la struct de manière cohérente avec le langage.

en raison des interruptions d'interprétation, le système doit rester robuste aux éléments déclarés en local non désalloués (utilisation des longjumps) comme c'est le cas actuellement

Modifications du pattern editor

Actuellement le pattern editor génère du code script qui est ensuite converti en byte code. Afin de simplifier les mises à jour liées aux patterns en évitant la génération puis la compilation, je propose que le pattern editor génère un byte code (simple) qui lui soit propre. Ce byte code adressera les différents patterns livrés sous forme de fonctions C++. Le "langage pattern" étant un langage beaucoup plus synthétique que le C++ ou le script, l'utilisation d'un byte code se justifie ici pleinement. Je ne connais pas suffisamment le pattern editor pour déterminer les changements que cela implique.

Dans cette idée, les gpp développent les fonctions de base adressables comme patterns qui sont compilés puis livrés. Le pattern devient alors disponible dans le langage pattern. Ces patterns pourraient être livrés sans une DLL spécifique communiquant avec le reste du système uniquement via l'API d'AI fonctions moteur.

Support multi DLLs

Dans le cas d'utilisation en plusieurs DLLs :

  • Une fonction que l'on veut exporter pour être utilisée depuis une autre DLL devra être déclarée virtuelle. Ainsi il est possible de l'appeler depuis une autre DLL sans avoir à l'exporter explicitement. Il me semble qu'il faut se préserver d'avoir à exporter les fonctions ou les variables explicitement dans les DLLs sinon cela rend le système beaucoup plus sensible à la méthode de déploiement (link statique/link dynamique et complexité de la déclaration des exports selon la plateforme...).

Note : ce principe inspiré de COM ne fonctionne pas avec les PRX sur PS3.

  • Le système d'initialisation doit être capable de chercher le code d'un gen model (ou sa factory) en parcourant plusieurs DLLs.

F     Néanmoins, si les patterns utilisent un système de byte code (cf Modifications du pattern editor 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350035003600390036003300330032000000 ) et étant donnés les temps de compilation actuels pour la lib, on peut se poser la question de l'intérêt du déploiement en multi DLLs.

Migration

Dans un premier temps il faudrait garder la compatibilité entre le système C++ généré et écriture C++ manuelle, en faisant évoluer le système complet car il est probable que l'on souhaite réutiliser le code AI script existant sans avoir à tout re-développer. D'autre part cela permet de migrer au moment voulu.

Certaines évolutions proposées peuvent cohabiter avec la génération de code. Voici une proposition de road-map.

les modifications qui sont liées apparaissent dans un cadre correspondant

les modifications côté moteur apparaissent en bleu

les modifications dans les scripts existants en saumon

Phase d'évolution commune avec la génération de code

Modifier les scripts pour éliminer les cas de macros différentes portant le même nom. Cela permettra d'activer le système de génération des macros (simples) dans les sources C++ produisant un code beaucoup plus clair

REF _Ref156278965 \r \h 3 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370038003900360035000000

Caractériser dans les fichiers .vas les modèles correspondant à des structures dans les scripts existants

8.2.3.1 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370036003200380030000000

Procéder aux évolutions de mapping des données pour unifier les classes modèles et modèle data :

Modifier l'interpréteur pour ménager l'espace nécessaire aux pointeurs me et pvftable

Modifier l'interpréteur pour appeler les factories de modèle à chaque création d'instance (remplacer l'agrégation de AI_cl_GenModelIntertace dans le AI_cl_Model par l'agrégation d'une factory AI_TModelFactory)

Modifier le générateur de code pour générer les variables dans les classes modèles et différencier la génération des structs

Modifier le générateur de code pour générer des model_cast stockés dans des GenModel* à la place des model_instance

8.2.3.1 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370036003200380030000000

Modifier les scripts pour éviter l'utilisation du système de partage de variables d'instance lorsque deux instances du même modèle sont instanciées sur deux tracks différentes du même gao

REF _Ref156278198 \r \h 8.2.3.2.2 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370038003100390038000000

Modifier l'interpréteur et l'implémentation de la DLL pour placer le numéro de track courante dans l'espace de variables d'instance en plus du me et du pvftable (cela permettra d'en disposer directement facilement pour les passages de contexte)

REF _Ref156278198 \r \h 8.2.3.2.2 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370038003100390038000000

Modifier le système de passage du contexte dans les AI fonctions :

Flager précisément les AI fonctions pour caractériser la nature du contexte utilisé par l'AI fonction.

Ajouter en premier(s) paramètre(s) du corps de l'AI fonction le contexte nécessaire ([gao], [model], [track]). Modifier le corps des AI fonctions concernées pour utiliser ces paramètres au lieu des variables globales de l'interpréteur.

Modifier l'interpréteur pour passer ces paramètres automatiquement en fonction des flags des AI fonctions (ainsi le système reste compatible avec le système script).

Modifier la génération de code pour passer les paramètres nécessaires. Enlever le système de contexte en début de fonction. Le remplacer par un système gérant seulement les stop interprétation (_stop_interpret_manager existant ou un système de fonction_ + fonction inline comme décrit précédemment)

REF _Ref156278198 \r \h 8.2.3.2.2 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370038003100390038000000

Ajouter une automation (OLE ?) de Visual depuis l'éditeur d'AI Yeti.

REF _Ref156280453 \r \h 8.1.1 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200380030003400350033000000

Phase d'évolution spécifique au code C++ écrit manuellement

Ajouter un mot clé permettant de spécifier les modèles écrits en C++ manuellement. Développer un générateur permettant de générer des squelettes de fichier .h et .cpp en fonction des .vas (à la manière du générateur de code Rose cpp de Rational Rose)

REF _Ref156279259 \r \h 8.2.1 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370039003200350039000000

Développer le système permettant d'importer des références sur data dans du code C++ écrit manuellement. Ce système peut facilement cohabiter avec le système généré en réservant des plages d'index dédiées à la génération et au code "manuel".

REF _Ref156280684 \r \h 8.2.2 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200380030003600380034000000

Modifier le système d'initialisation de la DLL dans AI_cl_DLLManager pour pouvoir adresser des classes dans différentes DLL.

REF _Ref156279713 \r \h 8.2.6 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200370039003700310033000000

Modifier le système pattern editor pour adresser des patterns existants en C++ dans une DLL dédiée plutôt que générer du code script (création d'un byte code spécifique aux patterns).

REF _Ref155696332 \r \h 8.2.5 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350035003600390036003300330032000000

Développement des patterns utilisés sous forme C++ dans une DLL dédiée

REF _Ref155696332 \r \h 8.2.5 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350035003600390036003300330032000000

Génération ultime des sources C++ à partir des scripts. Archivage sur une base perforce dédiée

F     Dès lors qu'une partie des AI sont écrites directement en C++, le système n'aura plus à être exécuté directement en scripts

Développer un ensemble de containers plus fiables et ouverts (string et tableaux) en faisant évoluer le système existant

REF _Ref156281368 \r \h 8.2.4 08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F005200650066003100350036003200380031003300360038000000

Optimisation du code moteur en désactivant tout ce qui est devenu inutile dans l'interpréteur, la génération de code.

F     Les évolutions futures des features du système sont simplifiées puisque les évolutions du langage n'ont plus à être répliquées dans l'interpréteur et le système de génération. Ces features peuvent tirer partie de la puissance du langage C++ et des debuggers existants. Exemple :

ajouter une définition dans les fichiers .vas permettant de déclarer des pointeurs model castés dans les classes


Document Info


Accesari: 3416
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )