[Tutorial] Nombre de paramиtres variable (introduction а l'assembly)
#1

Yo les gens.

C'est un tutoriel qui servira pas mal aux intermйdiaires et avancйs, beaucoup moins aux dйbutants (mais on sait jamais, peut-кtre que le dйbutant est assez chanceux pour йtudier des fonctions avec un nombre de paramиtres variable). Ce tutoriel est йgalement (selon moi) une bonne introduction а l'assembly. Il reste toutefois assez ciblй et ne remplace en rien le tutoriel de Y_Less а propos de l'utilisation du #emit.
Dans le tutoriel, je considиre que vous savez ce que vous faоtes en pawn. Je n'expliquerais donc pas les natives basiques, simplement pourquoi je les utilise.

Alors avant tout, j'explique le thиme de ce tutoriel : apprendre а manipuler les paramиtres variables. Alors je pas encore super douй dans la matiиre, mais je me dйbrouille.

Ce tutoriel vous expliquera UNE SEULE des deux faзons, celle que je sais utiliser, pour manipuler les paramиtres variables. Sachez qu'une deuxiиme faзon existe, mais est beaucoup plus compliquйe а mettre en place.

Mais c'est quoi concrиtement des paramиtres variables ?

"Concrиtement", dire "paramиtres variables" зa veut rien dire. Mais une "fonction а nombre de paramиtres variables", c'est une fonction qui peut кtre utilisй avec un nombre de paramиtres non fixe.

Par exemple, la fonction la "plus utilisйe" avec un nombre de paramиtres fixe c'est "SendClientMessage" (la fonction de base) : toujours 3 paramиtres (le joueur qui reзoit le message, la couleur du message et le message а envoyer).

Par contre, la fonction la "plus utilisйe" avec un nombre de paramиtres non fixe c'est "format".
C'est vrai : il n'y a pas toujours le mкme nombre de paramиtres passйs а la fonction "format", c'est pour cela que dans le prototype de la fonction, vous avez ceci : "{Float, _}:...".

Si vous avez pas compris ce qu'est ce symbole bizarre, c'est pas grave. Le fait est que vous l'utilisez souvent. Mais puisque ce tutoriel est lа pour vous aider а "apprendre des choses utiles", et parce que c'est pas compliquй du tout а expliquer comme а comprendre, voici l'explication :

Comme en mathйmatiques, les { } servent а dйterminer des ensembles (ou pour les tableaux aussi). Eh bien lа, c'est pareil ! Ca veut dire que les paramиtres qu'on peut passer а la fonction "format" peuvent avoir un des tags listйs dans l'ensemble. Ici, la fonction "format" accepte les variables avec le tag "Float" ou avec aucun tag du tout ("_").

A ce sujet, plusieurs utilisateurs du forums ont modifiй la fonction format pour lui faire accepter d'autres types de variables, telles que les (Player)Text(3D) (Y_Less) ou encore les string packйes (Emmet_).

D'ailleurs, je sais par expйrience que beaucoup ont essayй de faire une fonction "SendClientMessageFormatted(playerid, couleur, message[], {Float,_}:...}", mais tous ont butй au mкme endroit (je reprends l'exemple de Y_Less pour y_va) :

PHP Code:

stock SendClientMessageFormatted
(playeridcouleurmessage[], {Float,_}:...}
{
      new 
string[145]; // La taille maximale d'un message est 144 + 1 pour le nullbyte
      
format(stringsizeof(string), "%s // Et aprиs, je fais comment ? 
Y_Less a crйй une librairie pour ce genre de fonctions. Elle s'appelle y_va et est disponible dans YSI 3.1 ou YSI 4 (la version de YSI 3.1 est plus lente que celle de YSI 4 mais est garantie 100% fonctionnelle).
Mais concrиtement, зa n'explique pas vraiment comment la fonction fait pour rйcupйrer les paramиtres, puisqu'ils ne sont, comme je l'ai dit, jamais identiques.

Alors pour зa, on va se servir du dйmon de tous ceux qui m'ont dйjа parlй, la directive "#emit" (je dis "tous ceux", je gйnйralise, mais beaucoup m'ont parlй en privй et se demandaient comment utiliser cette directive, pourquoi faire, etc. Contrairement а ce qu'on pense, c'est assez facile une fois qu'on sait ce qu'on fait). Je rйpиte, je ne maоtrise pas encore (et je pense кtre loin de maоtriser) l'assembly, donc je ne pourrais vraiment pas aller plus loin que ce que j'explique dans le tutoriel.

Notez bien que je n'expliquerais qu'un minimum les directives utilisйes, car ce tutoriel n'est pas fait pour apprendre а se servir de #emit, mais fait pour apprendre а utiliser #emit pour rйcupйrer des paramиtres variables.

On commence !

Prenons l'exemple citй plus haut : un SendClientMessageFormatted.

Le header sera йvidemment celui lа :

pawn Code:
stock SendClientFormattedMessage(playerid, couleur, const message[], {Float,_}:...)
On commence par crйer les variables dont on aura besoin au dйbut :

PHP Code:

 
static const
    
ARGUMENTS 3// 3 correspond au nombre d'arguments statiques (le nombre d'arguments minimums pour SendClientMessage). On l'initialise en statique constante car ce nombre ne changera jamais, peu importe oщ et quand la fonction sera appelйe
    
new
    
= (numargs() - ARGUMENTS) * BYTES_PER_CELL// n correspond au nombre d'arguments supplйmentaires (ceux passйs aprиs le "message", soit а quoi correspondent les %d, %f, %x, %i, %s, etc. BYTES_PER_CELL vaut 4 en pawn
    
if (!n) return SendClientMessage(playeridcouleurmessage); // On vйrifie si n vaut zйro, soit si il n'y a eu que 3 arguments passйs а la fonction. Si oui, on envoie directement le message sans aller plus loin, petite optimisation 
Une fois les premiиres йtapes de vйrification passйes, on poursuit en indiquant lа oщ la fonction doit s'arrкter (soit le dйbut et la fin de la recherche)

PHP Code:

 
new
    
fstring[145],
    
arg_start,
    
arg_end;
   
    
#emit CONST.alt        message // On transforme l'enregistrement alternatif en la valeur de message
    #emit LCTRL          5 // L'enregistrement primaire prend la valeur de l'enregistreur "frame pointer"
    #emit ADD // L'enregistreur primaire devient ce qu'il y a dans l'enregistreur alternatif (soit la valeur de message) + ce qu'il contient dйjа (soit la valeur du frame pointer)
    #emit STOR.S.pri        arg_start // On stocke зa dans arg_start
   
    #emit LOAD.S.alt        n // L'enregistreur alternatif devient la valeur du frame pointer + n
    #emit ADD // Dйjа expliquй au dessus
    #emit STOR.S.pri        arg_end // On stocke зa dans arg_end 
Ensuite, on rйcupиre les arguments et on les PUSH (ajoute au sommet de la pile)

PHP Code:

do
{
    
#emit LOAD.I // L'enregistreur primaire prend sa valeur complиte
    #emit PUSH.pri // On push l'argument rйcupйrй
    
arg_end -= BYTES_PER_CELL// On retire а arg_end la valeur en mйmoire d'un argument, soit 4 bytes
    #emit LOAD.S.pri      arg_end // Dйjа expliquй plus haut mais а adapter pour l'enregistreur primaire
}
while (
arg_end arg_start); // Et on rйpиte l'opйration tant qu'il reste des arguments а PUSH. Une boucle while aurait suffit mais une boucle do while est plus adaptйe aux standards de la programmation dans ce cas lа : la boucle sera exйcutйe au moins une fois et comme on est sыrs que l'utilisateur a passй au moins 4 paramиtres, on peut utiliser cette boucle. 
Allez, on y est presque ! Si vous avez suivi jusqu'ici, vous avez fait le plus gros !
Maintenant, on PUSH les arguments statiques (ou les arguments obligatoires, ceux que le codeur est obligй de passer)

PHP Code:

#emit PUSH.S          message // On PUSH le message de base, contenant les %[specifieur] dans le stack
#emit PUSH.C          145 // On PUSH la taille maximale, soit la mкme que celle de "fstring", dans le stack en tant que constante
#emit PUSH.ADR       fstring // On PUSH la destination
+= BYTES_PER_CELL 3// On rajoute 12 bytes а n, soit la taille des trois premiers arguments obligatoires, ceux qu'on a pas comptй dиs le dйbut
#emit PUSH.S          n // On PUSH n dans le stack
#emit SYSREQ.C         format // On appelle la native "format" via SYSREQ (en tant que constante). Notez bien que SYSREQ ne sert qu'а appeler des NATIVES. Pour les fonctions crйes soi mкme, on utilisera CALL 
Et enfin, on nettoie le stack aprиs l'opйration et on envoie le message

PHP Code:

+= BYTES_PER_CELL;
#emit LCTRL          4 // L'enregistreur primaire prend la valeur de l'index du stack
#emit LOAD.S.alt        n // Dйjа expliquй au dessus
#emit ADD // Idem
#emit SCTRL          4 // On transforme l'index du stack en la valeur de l'enregistreur primaire, soit vide
   
return SendClientMessage(playeridcouleurfstring); // On envoie le message formattй au joueur avec la couleur spйcifiйe 

Le code entier donnerait donc :

PHP Code:

stock SendClientFormattedMessage
(playeridcouleur, const message[], {Float,_}:...)
{
    static const
    
ARGUMENTS 3;
    new
    
= (numargs() - ARGUMENTS) * BYTES_PER_CELL;
    if (!
n) return SendClientMessage(playeridcouleurmessage);
   
    new
    
fstring[145],
    
arg_start,
    
arg_end;
   
    
#emit CONST.alt        message
    #emit LCTRL          5
    #emit ADD
    #emit STOR.S.pri        arg_start
   
    #emit LOAD.S.alt        n
    #emit ADD
    #emit STOR.S.pri        arg_end
   
    
do
    {
        
#emit LOAD.I
        #emit PUSH.pri
        
arg_end -= BYTES_PER_CELL;
        
#emit LOAD.S.pri      arg_end
    
}
    while (
arg_end arg_start);
   
    
#emit PUSH.S          message
    #emit PUSH.C          145
    #emit PUSH.ADR       fstring
   
    
+= BYTES_PER_CELL 3;
    
#emit PUSH.S          n
    #emit SYSREQ.C         format
   
    
+= BYTES_PER_CELL;
    
#emit LCTRL          4
    #emit LOAD.S.alt        n
    #emit ADD
    #emit SCTRL          4
   
    
return SendClientMessage(playeridcouleurfstring);

Et cette opйration est а faire а chaque fois que vous voulez utiliser une fonction а paramиtres variables.
Par exemple, si je veux faire une "copie" de la fonction printf (une macro quoi, mais j'aime me compliquer la vie comme certains le disent), je devrais procйder comme montrй ci-dessus mais en modifiant quelques trucs.

Donc l'exercice de ce tutoriel (je l'ai dйjа proposй а DZX mais il a pas rйussi oh le mйchant) sera de coder une "copie" de la fonction printf (printf_ par exemple).

Comme d'hab, aucun scroll down si vous voulez pas vous faire spoil.





















PHP Code:

stock printf_
(string[], {Float,_}:...)
{
    static const 
ARGUMENTS 1;
    new 
= (numargs() - ARGUMENTS) * BYTES_PER_CELL;
    if(!
n) return printf(string);
    new 
        final[
1025], // printf ne peut pas afficher plus de 1024 caractиres, donc on rajoute 1 pour le nullbyte et on a la taille de notre tableau !
        
arg_start,
        
arg_end,
    ;
    
#emit CONST.alt                    final
    #emit LCTRL                        5
    #emit ADD
    #emit STOR.S.pri                   arg_start
    #emit LOAD.S.alt                   n
    #emit ADD
    #emit STOR.S.pri                   arg_end
    
do
    {
        
#emit LOAD.I
        #emit PUSH.pri
        
arg_end -= BYTES_PER_CELL;
        
#emit LOAD.S.pri               arg_end
    
}
    while (
arg_end arg_start);
    
#emit PUSH.S                       string
    #emit PUSH.C                       1025
    #emit PUSH.ADR                     final
    
+= BYTES_PER_CELL;
    
#emit PUSH.S                       n
    #emit SYSREQ.C                     format
    
+= BYTES_PER_CELL;
    
#emit LCTRL                        4
    #emit LOAD.S.alt                   n
    #emit ADD
    #emit SCTRL                        4
    
return printf(final);

ЙDIT : J'ai remarquй que mon code peut кtre optimisй en un point. Je l'ai pas fait involontairement mais c'est un mal pour un bien puisque зa sert de conclusion : tentez d'optimiser la fonction printf_ que j'ai codйe. L'opti est pas dure а faire mais compliquйe а trouver. Bonne chance !

Voilа ! Vous pouvez aussi coder une fonction qui fera le "travail" а votre place en utilisant "funcidx" mais зa implique que votre fonction а nombre d'arguments variable soit une fonction publique. Enfin bref, j'espиre que vous avez appris plein de trucs sur l'assembly et sur la gestion des arguments (j'espиre plus sur l'un que sur l'autre).
Si vous avez des questions, des rйclamations (car je peux m'кtre trompй, errare humanum este lol), ou quoi que ce soit, rйpondez simplement а la suite du tutoriel, j'essayerais d'y rйpondre au mieux.
Si vous voulez d'autres exemples d'utilisation de l'assembly, demandez simplement.

Ah oui, avant que j'oublie,

REMERCIEMENTS SPЙCIAUX :

Sasuke78200/Susuki78200/Ssk pour toute l'aide qu'il a pu m'apporter
Y_Less pour son tutoriel de base sur #emit (qui m'a appris pas mal de choses)
Toutes les personnes qui ont codй des trucs en p-code dont les codes m'ont aidй а progresser dans le domaine

NB : Prenez des gants quand vous utilisez cette directive : ayez toujours la documentation en tкte ou sous les yeux. Le compileur ne dйtecte pas les erreurs de codage dans ces directives, c'est donc assez facile de foutre en l'air un code complet juste а cause d'une seule directive foireuse. A vos risques et pйrils si vous expйrimentez зa en dehors du tutoriel !
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)