Posté le: 17/09/09 04:08 Sujet du message: Tirer parti des hashtables
L'objectif de ce tutoriel est d'apprendre au lecteur à se servir des hashtables à son avantage; quels sont les moyens communs pour y arriver ?
Il doit aussi servir de remplaçant à feu le tutoriel "Rustinage au Game Cache", qui lui avait besoin de bien plus qu'un petit rustinage.
Les liens utiles se trouvent en fin de topic. Notez qu'on utilisera dans l'exemple des notions de vJass ainsi que TimerUtils, parce que le recyclage, c'est le bien.
I/ Ce que sont les hashtables
a) Définition et explications
Une hashtable est un nouveau type d'objet introduit dans la mise à jour 1.23b, en même temps que la suppression (supposée) du return bug. Pour information, celui-ci permettait de faire du transtypage facilement, et a été retiré car il permettait d'éxécuter du code agissant à un niveau supérieur au JASS, donc pouvant installer un virus sur votre ordinateur par exemple. Nous y avons donc également perdu la fameuse fonction H2I, qui trouve son remplaçant dans la fonction GetHandleId que nous verrons dans un instant.
La hashtable permet de stocker une valeur (qu'elle soit entière, réelle, booléenne, ou une extension de handle) sous deux chiffres, que l'on apellera dossier et sous-dossier pour relier ça à la gestion de fichier de certains OS. Contrairement au Game Cache, dont les hashtables sont les remplaçantes, le dossier et le sous-dossier sont définis par un entier chacune, et non par une chaîne de texte. Cela signifie donc moins de conversions et donc une vitesse supérieure (il faut y ajouter le fait que le GC est juste plus lent dans son éxécution). Considérez les hashtables comme un moyen de créer des variables de façon dynamique.
b) Comment s'en servir
On a donc plusieurs possibilités :
Sauvegarder une donnée dans une hashtable, via une fonction telle que celle qui suit :
Vérifier qu'une valeur est stockée; je ne vois personnellement pas d'utilité personnellement, puisque que, comme nous l'indique le GUI, si il n'y a pas de donnée, alors la valeur renvoyée est null/0. EDIT : C'est plus propre pour s'en servir dans des if etc. PPS : TB tient à ce que je fasse remarquer que évidemment si vous avez stocké la valeur par défaut, ça peut servir. Par ailleurs, ceci est légèrement plus lent que de charger la valeur pour la vérifier.
Bien sûr, ces fonctions existent pour d'autres types que des entiers; cependant, puisque les structs du vJass sont des entiers, on se servira le plus souvent d'entiers dans ce qui sera expliqué plus loin.
II/ Tirer parti des hashtables
a) Quelques explications
Comme vous le savez probablement si vous faites du JASS, TriggerSleepAction et son petit frère PolledWait partagent imprécision, impossibilité d'utilisation dans des conditions, risque de corruption de déclencheur. Pour compenser cela, la solution trouvée est d'utiliser les timers, qui sont bien plus précis. Cependant, comment stocker des données à réutiliser plus tard ?
C'est là que les hashtables interviennent : En stockant des données dans le dossier / sous-dossier GetHandleId(votreTimer), vous pouvez les récupérer plus tard en vous référant à ce timer. Par exemple avec GetHandleId(GetExpiredTimer()). Un handle étant par définition unique, ce que renverra la fonction GetHandleId le sera aussi; pas de risques que le timer a se retrouve à utiliser les données du timer b.
Un des autres éléments qui a souvent besoin d'avoir des données associées est le trigger. De la même façon, vous pouvez utiliser GetHandleId(votreDeclencheur).
A savoir : Les hashtables ne se plantent pas quand elles récupèrent les données, si vous chargez un joueur à un endroit donné et que c'est une unité qui s'y trouve, alors vous ne verrez que l'ombre d'un null. Cela implique également que l'on ne peut stocker deux données étendant le même type de base (entier, réel, booléen, handle) sur le même emplacement (source : Blizzard).
Pour ce tutorial, nous tenterons de créer la fonction TriggerApplyTimedLife. Comme son nom l'indique, il s'agît de provoquer la destruction d'un trigger après un temps donné.
b) Un exemple : TriggerApplyTimedLife
Créons donc cette fonction. Nous avons besoin de savoir de quel déclencheur on parle, ainsi que la durée restante à ce timer pour s'éxécuter.
Jass:
library TriggerApplyTimedLife requires TimerUtils
function TriggerApplyTimedLife takes trigger trig, real timeout returns nothing
endfunction
endlibrary
Première chose : Nous avons besoin d'une hashtable. Etant donné qu'il s'agît d'un code qui sera exposé au public, nous utiliserons une hashtable spécialement pour cette library, qui sera donc (presque) autonome. Pour ce que j'en sais, il n'y a pas eu de benchmarking pour comparer l'efficacité d'une grande et unique hashtable face à plusieurs hashtables contenant moins de données. Ceci étant dit, il n'y a pas de limite connue au nombre de hashtables que vous pouvez initialiser en même temps, contrairement au game cache ne pouvant exister en plus de 256 exemplaires. EDIT : Le nombre de hashtables pouvant exister en même temps sur une même carte est de 256 également. Evitez donc de les créer de façon dynamique.
Faisons donc cela : Nous allons l'initialiser dans un initializer séparé grâce à la fonction InitHashTable; je n'ai pas encore fait de test pour savoir si initialiser une hashtable dans sa déclaration en tant que globale fonctionne ou non. EDIT : Je l'ai fait, ça marche.
globals
private hashtable hasht //La hashtable dont nous allons nous servir
endglobals
function TriggerApplyTimedLife takes trigger trig, real timeout returns nothing
endfunction
private function Init takes nothing returns nothing
set hasht = InitHashTable() //Initialise la hashtable et la met à la bonne valeur
endfunction
endlibrary
Une fois que cela est fait, nous pouvons donc passer au codage lui-même. Nous avons besoin de créer un timer (que nous apellerons tim ici), de lui associer la valeur de trig dans la hashtable hasht et de lancer le timer pour qu'il expire dans timeout secondes et active la fonction TimeExpired.
globals
private hashtable hasht //La hashtable dont nous allons nous servir
endglobals
private function TimeExpired takes nothing returns nothing
endfunction
function TriggerApplyTimedLife takes trigger trig, real timeout returns nothing
local timer tim = NewTimer() //On crée un nouveau timer via TimerUtils
call SaveTriggerHandle(hasht, 0, GetHandleId(tim), trig) //On sauve le déclencheur dans un endroit où on pourra le retrouver en ayant le timer
call TimerStart(tim, timeout, false, function TimeExpired) //On démarre le timer
endfunction
private function Init takes nothing returns nothing
set hasht = InitHashTable() //Initialise la hashtable et la met à la bonne valeur
endfunction
endlibrary
Parfait, maintenant nous pourrons retrouver le trigger associé. Codons donc la fonction TimeExpired, apellée à l'expiration de tim.
On en profitera pour ajouter une sécurité, afin d'éviter que l'utilisateur fasse n'importe quoi avec la fonction.
globals
private hashtable hasht //La hashtable dont nous allons nous servir
constant real TRIGGER_TIMED_LIFE_DEFAULT_TIMEOUT = 3. //Si l'utilisateur donne une valeur incorrecte, voilà la valeur donnée
endglobals
private function TimeExpired takes nothing returns nothing
local timer tim = GetExpiredTimer() //On récupère tim
local trigger trig = LoadTriggerHandle(hasht, 0, GetHandleId(tim)) //On récupère trig là où on l'a stocké
call RemoveSavedHandle(hasht, 0, GetHandleId(tim)) //Puisque l'on a plus besoin d'avoir la valeur stockée, on nettoie pour éviter un leak
call ReleaseTimer(tim) //Plus besoin du timer, on le relâche, toujours avec TimerUtils
call DestroyTrigger(trig) //On fait enfin ce que l'on voulait faire
set trig = null //Anti-leak tout ça
endfunction
function TriggerApplyTimedLife takes trigger trig, real timeout returns timer
local timer tim
if (trig == null) then //S'active si le déclencheur donné est nul
debug call BJDebugMsg("Warning: TriggerApplyTimedLife was given an incorrect trigger. Operation cancelled.")
return null
endif
if (timeout <= 0.) then //S'active si le temps est mal réglé
set timeout = TRIGGER_TIMED_LIFE_DEFAULT_TIMEOUT
endif
set tim = NewTimer() //On crée un nouveau timer via TimerUtils
call SaveTriggerHandle(hasht, 0, GetHandleId(tim), trig) //On sauve le déclencheur dans un endroit où on pourra le retrouver en ayant le timer
call TimerStart(tim, timeout, false, function TimeExpired) //On démarre le timer
return tim //On renvoie le timer pour qu'il soit détruit en même temps que le trigger par l'utilisateur
endfunction
private function Init takes nothing returns nothing
set hasht = InitHashTable() //Initialise la hashtable et la met à la bonne valeur
endfunction
endlibrary
Et voilà votre fonction TriggerApplyTimedLife toute prête Il y a peut-être des risques d'échec à cause de la façon dont sont allouées les handles, que je n'ai pas étudiée. Grossièrement, en l'état actuel de la fonction, si le trigger est détruit avant que sa "timed life" se termine, il me semble qu'il est possible que son handle soit réalloué à un autre déclencheur qui risquerait alors une mort prématurée. PS :J'ai ajouté un renvoi de tim. Il suffit de le relâcher en même temps que la destruction du déclencheur pour éviter le bug sus-mentionné.
Bien sûr, cela aurait pu s'appliquer à bien d'autre situations. Vous auriez par exemple pu stocker une struct avec toutes vos données. A ce sujet, TimerUtils, dont on s'est servi dans l'exemple, possède des fonctions SetTimerData et GetTimerData. Si vous utilisez la version bleue de TimerUtils (plus lente mais plus flexible), il s'agît d'utilisation des hashtables. Si vous utilisez la version rouge ou orange, les fonctions sus-citées sont plus rapides que l'utilisation des hashtables. En plus vous n'avez pas besoin de nettoyer ensuite.
Brissou de Mourièssou Créateur de sorts, depuis 1936.
Inscrit le: 30 Aoû 2007 Messages: 1511 Sujets: 26 Spécialité en worldedit: Développer des jeux vidéos pour le fun, donc world edit c'est comme faire une addition. Médailles: 2 (En savoir plus...)
Posté le: 17/09/09 08:01 Sujet du message:
Faudrait également faire un chapitre pour nos amis du GUI, avec un exemple. C'est surtout eux qui ne comprennent pas du tout à quoi ça sert ni comment s'en servir, les pauvres n'ont jamais connu les "joies" du gamecache.
Faudrait mettre à jour la colorisation syntaxique pour les nouvelles fonctions. _________________
Tuto intéressant (à titre personnel pour moi qui suit directement passé du Gui au VJass et aux struct sans me servir du gamecache).
Question de mappeur: en gros les Hasthables permettent d'éviter de se servir des tables de Vexorian?
Demande d'édition du tutorial:
-Préciser que ce tutorial demande des connaissances en Jass, en VJass et le JNPG le plus récent ou alors donner des exemples en Gui.
-Donner des définitions (Qu'est-ce qu'un Handle par exemple; pour un débutant qui ne programme pas ailleurs que sur l'éditeur, je peux te garantir que c'est une notion bizarre au début)
-Mettre les liens des prérequis au début du tuto.
-Une carte d'exemple(s) serait appréciée _________________
Question de mappeur: en gros les Hasthables permettent d'éviter de se servir des tables de Vexorian?
Je ne connais la libraire sus-mentionnée qu'indirectement, mais il me semble que oui.
Apocalypse a écrit:
-Préciser que ce tutorial demande des connaissances en Jass, en VJass et le JNPG le plus récent ou alors donner des exemples en Gui.
Pour les exemples en GUI ça ne va pas le faire, c'est trop compliqué pour moi.
Je préciserais que l'exemple utilise des notions de vJass et donc recquiert le JassNewGenPack pour son fonctionnement. Cependant il n'est pas nécessaire d'utiliser ce logiciel pour utiliser les hashtables.
Apocalypse a écrit:
-Donner des définitions (Qu'est-ce qu'un Handle par exemple; pour un débutant qui ne programme pas ailleurs que sur l'éditeur, je peux te garantir que c'est une notion bizarre au début)
A vrai dire, le handle est une notion assez floue pour moi. Je m'en souviens comme étant une adresse vers un objet, mais ne m'étant pas intéressé à du code à un niveau supérieur au Jass, ça s'arrête là.
Apocalypse a écrit:
-Mettre les liens des prérequis au début du tuto.
Une mention "Des liens utiles se trouve en fin de topic" suffira-t-elle ? j'aime bien respecter les standards à la wikipedia
Apocalypse a écrit:
-Une carte d'exemple(s) serait appréciée
Ah euh.. ben je sortirais mon pack de héros.
Sinon je vais éditer dès maintenant parce qu'il y a une solution au petit bug de mon code. _________________
Inscrit le: 13 Oct 2007 Messages: 994 Sujets: 25 Spécialité en worldedit: Codeur
Posté le: 17/09/09 12:46 Sujet du message:
Un handle est en effet une adresse qui pointe sur un objet en mémoire.
En dehors du jass, les handles sont sources d'erreurs pour des débutants avec les différentes méthodes dont les arguments sont passés à une fonction.
Par exemple, en Jass, les arguments sont passés en copie de valeur si je me souviens bien. Un petit exemple sera sous doute mieux :
Jass:
function test takes integer i returns nothing
set i = i + 1
endfunction
Cette fonction ne fera rien. Car i est ce qu'on appel une "copie". On ne manipule donc qu'une copie de i, et pas i lui même. Il en va de même pour un pointeur. Par exemple :
Jass:
function test takes handle h returns nothing
set h = un_handle
endfunction
De même cette fonction est inutile. Par contre, l'objet qui est pointé par l'handle lui reste toujours le même. Ainsi :
Jass:
function test takes handle h returns nothing
call DestroyHandle(h)
endfunction
Cette fonction détruira bien l'objet pointé par h (DestroyHandle n'existe pas hein, c'est juste pour l'exemple). Mais h lui reste entièrement le même ! Il pointe toujours au même endroit dans la mémoire.
En résumé :
Jass:
function test takes handle h, integer i returns nothing
set i = i + i //N'a aucun effet en dehors de la fonction
call DestroyHandle(h) //Détruit bien l'objet en mémoire. Mais h est toujours le même
set h = un_hande //N'a aucun effet en dehors de la fonction
endfunction
_________________
- La théorie c'est quand rien ne fonctionne mais tout le monde sait pourquoi.
- La pratique c'est quand tout fonctionne mais personne ne sait pourquoi.
- Chez moi la théorie et la pratique sont réunies, rien ne fonctionne et personne ne sait pourquoi.
Inscrit le: 23 Aoû 2007 Messages: 7146 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 17/09/09 16:31 Sujet du message:
Citation:
Question de mappeur: en gros les Hasthables permettent d'éviter de se servir des tables de Vexorian?
Ne nous embrouillons pas, quasiment toutes les fonctions de Table sont inlinés, et c'est plus sexy d'utiliser Table que directement les fonctions natives des hashtables.
C'est utile pour attacher des entiers (instances de struct ou autre) à un handle (unité par ex)
Toutefois Table ne permet pas tout, et ce que je trouves débile, c'est que l'on soit obligé d'utiliser un string et non un entier en premier paramètre quand on veut utiliser une hashtable sous forme d'array 2D.
De même si on veut utiliser une hashtable comme une variable déployée sans limitation d'index autre qu'un integer, Table n'est d'aucun secours. _________________
Ne nous embrouillons pas, quasiment toutes les fonctions de Table sont inlinés, et c'est plus sexy d'utiliser Table que directement les fonctions natives des hashtables.
Question d'habitude je suppose. Les fonctions de manipulation des hashtables sont saiks imo. (c'est pas comme si ça faisait trois ans que je me sers de fonctions similaires hein ?) _________________
De toute façon, comme la plupart des codes partagés, je n'ai aucune vergogne à les éditer pour que cela ne me satisfasse pleinement.
(les points négatifs cités plus haut) _________________
Une mention "Des liens utiles se trouve en fin de topic" suffira-t-elle ? j'aime bien respecter les standards à la wikipedia
PS : Je me permets de poster un petit script que j'ai fait pour savoir si des valeurs de ma hashtable (nommée hasht, remplacez ça si la votre est appelée autrement) leakent ou non. Evidemment si vous avez plus de hashtables il vous faut plus de modifs..
Secret:
Jass:
scope CompterValeursHashT initializer InitTrig
globals
private integer count = 0
endglobals
private function IncreaseCount takes hashtable hasht, integer parentKey, integer childKey, integer value returns nothing
if not HaveSavedInteger(hasht, parentKey, childKey) then
set count = count + 1
endif
endfunction
debug hook SaveInteger IncreaseCount
private function DecreaseCount takes hashtable hasht, integer parentKey, integer childKey returns nothing
if HaveSavedInteger(hasht, parentKey, childKey) then
set count = count - 1
endif
endfunction
debug hook RemoveSavedInteger DecreaseCount
//===========================================================================
private function InitTrig takes nothing returns nothing
local integer i = 0
local trigger trig = CreateTrigger()
loop
call TriggerRegisterPlayerChatEvent(trig, Player(i), "-debugmode CountHashTValues", true)
exitwhen i == 11
set i = i + 1
endloop
call TriggerAddCondition(trig, Condition(function Trig_Actions))
endfunction
endscope
Une fois que le script est dans la carte, il suffit que n'importe quel joueur tape "-debugmode CountHashTValues" (insensible à la casse) et il pourra voir combien d'entiers sont stockés dans hasht au moment où il a tapé le message. _________________
Dernière édition par Bantas le 21/09/09 16:14; édité 1 fois
Inscrit le: 23 Aoû 2007 Messages: 7146 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 21/09/09 16:01 Sujet du message:
Mais les hook en debug, pour le speed freak powa.
Parce que des hook c'est quand même des appels de fonctions avec des TriggerEvaluate. _________________
Mais les hook en debug, pour le speed freak powa.
Parce que des hook c'est quand même des appels de fonctions avec des TriggerEvaluate.
Ouais. En théorie je suis sensé désactiver le script quand je suis en dehors des versions debug m'enfin..
Je vais en profiter pour essayer si debug marche avec //! novjass. J'ai des doutes mais ça vaut le coup d'essayer. Je sais que la balise ne devrait pas servir à ça m'enfin bon, de toutes façons sans jasshelper ça ne devrait pas marcher du tout.
Inscrit le: 23 Aoû 2007 Messages: 7146 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 21/09/09 16:30 Sujet du message:
TriggerRegisterAnyUnitEventBJ est l'une des BJ les plus utiles, et le mieux codé.
Faut pas tomber dans la phobie non plus.
Toutes les BJ ne sont pas evil, certaines sont utiles (sisi). _________________
TriggerRegisterAnyUnitEventBJ est l'une des BJ les plus utiles, et le mieux codé.
Faut pas tomber dans la phobie non plus.
Toutes les BJ ne sont pas evil, certaines sont utiles (sisi).
Je suis tout à fait d'accord sur le fait que TriggerRegisterAnyUnitEventBJ soit assez pratique, mais on parle de joueurs qui font des messages ici. _________________
Toutes les heures sont au format GMT + 1 Heure Aller à la page 1, 2Suivante
Page 1 sur 2
Vous ne pouvez pas poster de nouveaux sujets dans ce forum Vous ne pouvez pas répondre aux sujets dans ce forum Vous ne pouvez pas éditer vos messages dans ce forum Vous ne pouvez pas supprimer vos messages dans ce forum Vous ne pouvez pas voter dans les sondages de ce forum