FR/Debugage
Quand vous scriptez vous pouvez passer à côté de certaines erreurs qui ne sont pas apparentes au premier coup d’œil. Cette page va essayer de vous indiquer quelques démarches à suivre pour localiser les erreurs.
Console de Débogage
MTA a pour caractéristique de posséder une console de débogage qui comme son nom l'indique vous permet de déceler les erreurs dans les fonctions et scripts. Vous pouvez l'ouvrir en tapant debugscript x dans la console, ici x est le niveau de débogage:
- 1: seulement les erreurs
- 2: erreurs et warnings
- 3: erreurs, warnings et messages d'informations
Ainsi, en tapant debugscript 3 tout les messages seront visibles, cependant le niveau 2 est recommandé la plupart du temps pour le débogage. Quand vous testez vos scripts vous devez avoir le debugscript à disposition , cela vous permettra de détecter facilement les fautes d'orthographes dans les fonctions ou toutes autres erreurs aussi simple.
Exemple
L'exemple ci-dessous contient deux erreurs:
if (getPlayerName(player) == "Fedor") outputChatbox("Hello Fedor") end
Quand ce bout de code va être chargé, debugscript va vous renvoyer à peu près ceci:
Cela signifie que le code ne peut-être analysé/traité, car il y a une erreur de syntaxe. On vous montre le répertoire où se trouve le fichier, vous pouvez donc voir dans quelle ressource ce dernier se trouve ('myResource' dans notre cas) et bien sûr on vous donne le nom du fichier en question. Après le nom du fichier, on a le numéro de la ligne et encore après l'erreur/problème en question. C'est bien plus facile à résoudre maintenant, il manque juste 'then':
if (getPlayerName(player) == "Fedor") then outputChatbox("Hello Fedor") end
A présent le bout de code se charge et ne renvoie aucune erreur, jusqu'à ce qu'un joueur avec le pseudo 'Fedor' fasse son apparition. Le debugscript va nous renvoyer alors:
Cela signifie que la fonction appelée n'existe pas, cela s'explique facilement par le fait que le nom de la fonction est outputChatBox (avec une capitale B):
if (getPlayerName(player) == "Fedor") then outputChatBox("Hello Fedor") end
Ceci n'est bien sûr qu'un exemple, il y a une multitude d'autres erreurs et problèmes, mais vous avez au moins une idée du débogage.
Debug logging
You can also turn debug message logging on by editing coreconfig.xml in your GTA\MTA folder. You should find the following tag:
<debugfile/>
Replace that with a tag specifying the file you want to log messages to (file path is relative from the GTA folder):
<debugfile>MTA\debugscript.log</debugfile>
All debug messages will be appended to the specified file from now on. To turn logging off, replace that line with an empty tag again.
Astuces de déboguage
Au lieu de foncer tête baisser dans votre code, il existe d'autres stratégies pour débusquer les erreurs. La plupart d'entres elles utilisent des messages/sorties (au niveau de la console) de déboguage qui sont différent suivant les situations.
Fonctions Utiles
Pour commencer, il y a quelques fonctions qui peuvent être utiles pour le débogage.
- outputDebugString ou outputChatBox pour afficher des informations.
- tostring() sur une variable pour la convertir en chaîne de caractères, par exemple lorsque c'est un booléen.
- getElementType pour vérifier le type d'un élément de MTA.
Ajouter un message pour vérifier quand une condition if, when ou how often est executée
Un exemple typique qui vérifie si une condition ifest executée ou non. Il vous suffit d'ajouter un message que vous reconnaîtrez lorsqu'il s'affichera quand la condition sera executée.
if (variable1 == variable2) then outputDebugString("Condition vérifiée") -- Instruction end
Une autre pratique serait de vérifier quand est-ce que la valeur de la variable est modifiée. Il faut pour cela chercher le moment où la variable est modifiée et ajouter une fonction qui permet l'affichage d'un message juste après.
Afficher un message pour vérifier la "valeur" d'une variable
Prenons le cas ou vous voulez créer un checkpoint (marqueur), mais ce dernier n'apparaît pas à la position attendu. La première chose que vous allez faire sera de vérifier si la fonction createMarker est executée. Au lieu de faire ceci, vous pouvez vérifier les valeurs qui sont utilisées par la fonction createMarker.
outputChatBox(tostring(x).." "..tostring(y).." "..tostring(z)) createMarker(x,y,z)
Ce code aura pour conséquence de vous afficher un message avec les trois valeurs des variables qui sont utilisées en tant que coordonnées pour le marqueur. Vous pouvez maintenant comparer les valeurs obtenues avec celle désirées. Le tostring() vous permettra de vous assurer que les valeurs des variables seront transformées en "string" (chaîne de caractères), même si c'est un boléen.
Exemple
Imaginons que vous créiez un colshape (figure géométrique bi/tridimensionnelle pour détecter les collisions) quelque part et que vous souhaitiez qu'un joueur reste 10 secondes dans celle-ci avant d'éxecuter un code.
function colShapeHit(player) -- défini un timer pour afficher un message (le timer peut très bien éxécuter une fonction) -- stocke l'id du timer dans un tableau en utilisant le joueur (''player'') comme index/case. colshapeTimer[player] = setTimer(outputChatBox,10000,1,"Le joueur est depuis 10 secondes dans le colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- arrête le timer dès que le joueur quitte le colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Quand le joueur entre dans le colshape, le debugscript affiche le message suivant:
Cela signifie que vous voulez stocker une valeur dans un tableau (à la case player) qui n'existe pas. Dans l'exemple, le problème se pose quand vous voulez stocker l'id du timer dans le tableau. Nous devons donc vérifier si le tableau existe et si non en créer un.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- défini un timer pour afficher un message (le timer peut très bien éxécuter une fonction) -- stocke l'id du timer dans un tableau en utilisant le joueur (''player'') comme index/case. colshapeTimer[player] = setTimer(outputChatBox,10000,1,"Le joueur est depuis 10 secondes dans le colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- arrête le timer dès que le joueur quitte le colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Ceci fait il nous reste quand même un warning (avertissement).Attendez le message et quittez à nouveau le colshape:
À part ceci (nous en parlerons plus tard) tous semble bien fonctionner. Un joueur entre dans le colshape, le timer se lance, si il reste un message s'affiche, si il quitte le colshape le timer est arrêté.
Une erreur un peu plus complexe
Cependant, pour une quelconque raison le message est envoyé deux fois quand on reste dans le cercle de collision alors qu'on est dans un véhicule. Since it would appear some code is executed twice, we add debug messages to check this.Puisque c'est le cas on conclu qu'un code est exécuté deux fois, on va déboguer pour vérifier cela.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- ajout d'un message de debugage outputDebugString("colShapeHit") -- set a timer to output a message (could as well execute another function) -- store the timer id in a table, using the player as index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- ajout d'un message de debugage outputDebugString("colShapeLeave") -- kill the timer when the player leaves the colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Nous remarquons que les deux fonctions rattachées aux événements sont éxécutées deux fois, mais seulement une lorsque qu'on est à pied. On peut donc penser que le véhicule déclenche aussi les événements. Pour confirmer cette théorie , nous allons vérifier la variable player qui devrait contenir un élément joueur.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- ajout d'un message de debugage avec le type de l'élément outputDebugString("colShapeHit "..getElementType(player)) -- set a timer to output a message (could as well execute another function) -- store the timer id in a table, using the player as index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- ajout d'un message de debugage avec le type de l'élément outputDebugString("colShapeLeave "..getElementType(player)) -- kill the timer when the player leaves the colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Le message de débugage nous informe que, l'une des variables player est un joueur, et que l'autre, est un véhicule. Si nous voulons éxecuter notre code seulement quand un joueur entre dans le colshape, nous devons rajouter un if (condition) qui stoppera l'éxecution du code si ce n'est pas un joueur.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- add a check for the element type if (getElementType(player) ~= "player") then return end -- add a debug message, with the element type outputDebugString("colShapeHit "..getElementType(player)) -- set a timer to output a message (could as well execute another function) -- store the timer id in a table, using the player as index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- add a check for the element type if (getElementType(player) ~= "player") then return end -- add a debug message, with the element type outputDebugString("colShapeLeave "..getElementType(player)) -- kill the timer when the player leaves the colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Maintenant le code devrait fonctionner comme nous le souhaitions, mais nous avons toujours l'avertissement mentionné plus tôt. Cela est dû au fait que nous essayons d'arrêter un timer qui , quand le joueur est resté dans le colshape 10 secondes est éxecuté, n'existe plus. Il y a différentes façons de se débarasser de cet avertissement (si on part du principe qu'on sait qu'il n'existe plus à partir d'un certain moment et qu'on peut l'arrêter seulement si il ne s'est pas éxecuté). Une des méthodes serait de vérifier si le timer stocker dans le tableau existe. Pour faire cela, nous devons utiliser isTimer avant d'arrêter le timer.
if (isTimer(colshapeTimer[player])) then killTimer(colshapeTimer[player]) end
Le code final ressemblerait à ceci:
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- add a check for the element type if (getElementType(player) ~= "player") then return end -- add a debug message, with the element type outputDebugString("colShapeHit "..getElementType(player)) -- set a timer to output a message (could as well execute another function) -- store the timer id in a table, using the player as index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- add a check for the element type if (getElementType(player) ~= "player") then return end -- add a debug message, with the element type outputDebugString("colShapeLeave "..getElementType(player)) -- kill the timer when the player leaves the colshape if (isTimer(colshapeTimer[player])) then killTimer(colshapeTimer[player]) end end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Debugging Performance Issues
Si vous serveur utilise plus de ressource qu'il ne devrait ou si vous voulez vous assurez que votre code est efficace, vous pourrez trouver les brebis galeuses avec l'outil performancebrowser. Pour le lancer taper dans votre console "start performancebrowser" si cette ressource n'exite pas assurer de bien la posséder à l'emplacement adequat. Cet outil fourni un bon nombre d'information sur le debugage de perfomance. Fuite de mémoire, perte d'éléments et utilisation intensive du CPU sont facilement décelable via cet outil. Si vous utilisez l'option -d vous pourrez voir quelles fonctions utilisent de manière excéssive le CPU.
Pour accèder au performancebrowser vous devez entrer cette adresse dans votre navigateur de recherche: http://serverIPHere:serverHTTPPortHere/performancebrowser/ Noterque le / à la fin est obligatoire. Donc par exemple: http://127.0.0.1:22005/performancebrowser/ . Ensuite, vous devez vous logguer en jeu avec un compte administrateur ou un compte ayant accès à la ressource "general.HTTP" . La plupart des informations dont vous aurez besoin se trouvent dans les catégories "Lua timing" et "Lua memory". Chercher les valeurs qui sont plus élevées que les autres.
Exemples de code qui posent des problèmes de performances
L'ajout de données à un tableau sans les supprimer ensuite, même si ça prendra quelques mois ou années avant de poser des problèmes.
local someData = {} function storeData() someData[source] = true -- Rien ne gère le moment ou le joueur quitte le jeu, c'est donc considéré comme une perte de mémoire. -- Using the Lua timing tab you can detect the RAM usage of each resource. end addEventHandler("onPlayerJoin", root, storeData)
Element leaking is possible if you use temporary colshapes for whatever reason and may not destroy them. This would cause bandwidth, CPU and memory performance issues over time.
function useTemporaryCol() local col = createColCircle(some code here) if (normally this should happen) then destroyElement(col) end -- But sometimes it didn't so the script ended but the collision area remained and over time -- you may end up with hundreds to thousands of pointless collision areas. -- The Lua timing tab allows you to see the amount of elements each script has created. end
High CPU usage resulting in the server FPS dropping so much that the server is unplayable. In under 24 hours this can create havoc on a very busy server. The amount of "refs" in the Lua timing detect this type of build up, surprisingly the Lua timing tab didn't help in this case but Lua memory did.
addEventHandler("onPlayerJoin", root, function() -- Code for joiner addEventHandler("onPlayerQuit", root, function() -- Code for when they have quit -- See the problem? It's bound to root which the event handler is being added again and again and again end) end)
A function uses up a lot of your CPU because whatever it does takes a long time. This is just some function that takes a long time to complete. Without performancebrowser you'd have no idea its the cause but with performancebrowser you can see that a resource is using lots of CPU in the Lua timing tab. If you then enter: -d into the options edit box it will even tell you what file name and first line of the function that is using up so much CPU.
function someDodgyCode() for i=1, 100000 do -- some code end end