FR/Debugage: Difference between revisions

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
(Created page with "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émarc...")
 
No edit summary
Line 46: Line 46:
There are several strategies that support finding errors, apart from going through the code of course. Most of them include outputting debug messages, with differing information depending on the situtation.
There are several strategies that support finding errors, apart from going through the code of course. Most of them include outputting debug messages, with differing information depending on the situtation.


===Useful functions===
===Fonctions Utiles===
First of all some functions that may come in handy for debugging.
Pour commencer, il y a quelques fonctions qui peuvent être utiles pour le ''débogage''.
* [[outputDebugString]] or [[outputChatBox]] for outputting any information
* [[outputDebugString]] ou [[outputChatBox]] pour afficher des informations.
* [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] on a variable to turn it into a string, for example when it contains a boolean value
* [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] sur une variable pour la convertir en chaîne de caractères, par exemple lorsque c'est un booléen.
* [[getElementType]] to check an MTA Element for its type
* [[getElementType]] pour vérifier le type d'un élément de MTA.


===Ajouter un message pour vérifier quand une condition ''if'', ''when'' or ''how often'' est executée===
===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 ''if''est executée ou non. Pour faire ceci, il vous suffit d'ajoutez un message que vous reconnaîtrez lorsqu'il s'affichera quand la condition sera executée.
Un exemple typique qui vérifie si une condition ''if''est 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.
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
if (variable1 == variable2) then
if (variable1 == variable2) then
outputDebugString("entered if")
outputDebugString("Condition vérifiée")
-- Instruction
-- Instruction
end
end
Line 68: Line 68:
createMarker(x,y,z)
createMarker(x,y,z)
</syntaxhighlight>
</syntaxhighlight>
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 [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] vous permettra de vous assurer que les valeurs des variables seront transformées en "string" (chaîne), même si c'est un boléen.
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 [http://www.lua.org/manual/5.1/manual.html#pdf-tostring 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.


==Example==
==Exemple==
Imagine you created a colshape (collision shape) somewhere and you want a player to stay 10 seconds in it, then perform some action.
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.
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function colShapeHit(player)
function colShapeHit(player)
-- set a timer to output a message (could as well execute another function)
-- défini un timer pour afficher un message (le timer peut très bien éxécuter une fonction)
-- store the timer id in a table, using the player as index
-- stocke l'id du timer dans un tableau en utilisant le joueur (''player'') comme index/case.
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"Le joueur est depuis 10 secondes dans le colshape!")
end
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)


function colShapeLeave(player)
function colShapeLeave(player)
-- kill the timer when the player leaves the colshape
-- arrête le timer dès que le joueur quitte le colshape
killTimer(colshapeTimer[player])
killTimer(colshapeTimer[player])
end
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
</syntaxhighlight>
</syntaxhighlight>
When a player enters the colshape, debugscript outputs the following message:
Quand le joueur entre dans le colshape, le debugscript affiche le message suivant:
{{Debug error|..[path]: attempt to index global 'colshapeTimer' (a nil value)}}
{{Debug error|..[path]: attempt to index global 'colshapeTimer' (a nil value)}}
This means you tried to index a table that does not exist. In the example above, this is done when storing the timer id in the table. We need to add a check if the table exists and if not create it.
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.


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 95: Line 95:
colshapeTimer = {}
colshapeTimer = {}
end
end
-- set a timer to output a message (could as well execute another function)
-- défini un timer pour afficher un message (le timer peut très bien éxécuter une fonction)
-- store the timer id in a table, using the player as index
-- stocke l'id du timer dans un tableau en utilisant le joueur (''player'') comme index/case.
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"Le joueur est depuis 10 secondes dans le colshape!")
end
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)


function colShapeLeave(player)
function colShapeLeave(player)
-- kill the timer when the player leaves the colshape
-- arrête le timer dès que le joueur quitte le colshape
killTimer(colshapeTimer[player])
killTimer(colshapeTimer[player])
end
end
Line 108: Line 108:
</syntaxhighlight>
</syntaxhighlight>


Now we still get a warning when a player enters the colshape, waits for the message and leaves it again:
Ceci fait il nous reste quand même un ''warning'' (avertissement).Attendez le message et quittez à nouveau le colshape:


{{Debug warning|[..]: Bad argument @ 'killTimer' Line: ..}}
{{Debug warning|[..]: Bad argument @ 'killTimer' Line: ..}}

Revision as of 13:23, 18 April 2013

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:

INFO: Loading script failed: C:\<server path>\mods\deathmatch\resources\myResource\script.lua:15: 'then' expected near ´outputChatbox'

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:

ERROR: C:\<server path>\mods\deathmatch\resources\d\script.lua:15: attempt to call global 'outputChatbox' (a nil value)

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.

Debug strategies

There are several strategies that support finding errors, apart from going through the code of course. Most of them include outputting debug messages, with differing information depending on the situtation.

Fonctions Utiles

Pour commencer, il y a quelques fonctions qui peuvent être utiles pour le débogage.

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:

ERROR: ..[path]: attempt to index global 'colshapeTimer' (a nil value)

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:

WARNING: [..]: Bad argument @ 'killTimer' Line: ..

Except for that (we will talk about that later) everything seems to work fine. A player enters the colshape, the timer is started, if he stays the message occurs, if he leaves the timer is killed.

A more inconspicuous error

But for some reason the message gets outputted twice when you stay in the colcircle while in a vehicle. Since it would appear some code is executed twice, we add debug messages to check this.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- add a debug message
	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)
	-- add a debug message
	outputDebugString("colShapeLeave")
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

Now we notice that both handler functions get executed twice when we are in a vehicle, but only once when we are on-foot. It would appear the vehicle triggers the colshape as well. To confirm this theory, we check the player variable that should contain a player element.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	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 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)

The debug messages tell us that one of the player variables is a player, the other one a vehicle element. Since we only want to react when a player enters the colshape, we add an if that will end the execution of the function if it's not an player element.

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)

Now the script should work as desired, but will still output the warning mentioned above. This happens because the timer we try to kill when a player leaves the colshape will not exist anymore when it reached the 10 seconds and is executed. There are different ways to get rid of that warning (since you know that the timer might not exist anymore and you only want to kill it if it is there). One way would be to check if the timer referenced in the table really exists. To do this, we need to use isTimer, which we will use when we kill the timer:

if (isTimer(colshapeTimer[player])) then
	killTimer(colshapeTimer[player])
end

So the complete working code would be:

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

If your server is using up more resources than it should or you just want to make sure your scripts are efficient you can find the cause using a great tool that comes with MTA SA server, performancebrowser. Make sure that its started with "start performancebrowser" if it doesn't exist then get it from the default resources that come with the server. This tool provides an incredible amount of information for performance debugging. Memory leaks, element leaks and CPU intensive scripts are all easily findable via performancebrowser. If you use -d option in Lua timing you can see which functions are using up the CPU.

To access performancebrowser you will need to go to your web browser and enter the address: http://serverIPHere:serverHTTPPortHere/performancebrowser/ Note that the / at the end is required. So for example: http://127.0.0.1:22005/performancebrowser/ You will then need to login with an in-game admin account or any account that has access to "general.HTTP" Most of the information you will need are in the categories Lua timing and Lua memory, look for values that are much higher than other values.

Examples of scripts that could cause performance problems

Adding data to a table but never removing it. This would take months/years before it causes a problem though.

local someData = {}

function storeData()
    someData[source] = true
    -- There is no handling for when a player quits, this is considered a memory leak
    -- 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