RU/Debugging

From Multi Theft Auto: Wiki
Revision as of 17:12, 2 July 2010 by KGB (talk | contribs) (Translating)
Jump to navigation Jump to search

При написании скриптов вы обязательно столкнетесь с проблемами, которые не сможете решить немедленно. Цель этой страницы - показать вам основные способыы нахождения ошибок.

Консоль отладки

MTA включает консоль, которая показывает отладочные сообщения, выводимые функциями MTA и скриптами. Вы можете открыть ее введя debugscript x в консоли, где x уровень отладки:

  • 1: только ошибки
  • 2: ошибки и предупреждения
  • 3: ошибки, предупреждения и информационные сообщения

Так, введя debugscript 3, вы будете видеть все сообщения, этот или 2 уровень рекомендуется в большинстве случаев. Рекомендуем держать debugscript включенным всё время, пока вы тестируете свой скрипт, это поможет вам обнаружить мелкие ошибки и исправить их.

Пример

Этот участок кода содержит две ошибки:

if (getPlayerName(player) == "Fedor")
	outputChatbox("Hello Fedor")
end

При попытке запуска скрипта, содержащего этот участо кода, debugscript выведет что-то похожее на:

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

Это означает, что скрипт не может быть обработан из-за синтаксической ошибки. Показывается путь к скрипту, поэтому вы можете увидеть, какому ресурсу он принадлежит, ('myResource' в данном случае) и, конечно же, имя скрипта. После имени файла показан номер строки и информация об ошибке. Теперь проблема легко решается, мы просто забыли написать ключевое слово 'then':

if (getPlayerName(player) == "Fedor") then
	outputChatbox("Hello Fedor")
end

Теперь скрипт загрузится нормально и не будет выводить ошибок пока не выполнится для игрока с ником 'Fedor'. Тогда debugscript выведет:

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

Это значит, вызванная вами функция не существует, оно и понятно, ведь правильное название этой функции outputChatBox (с заглавной буквы B):

if (getPlayerName(player) == "Fedor") then
	outputChatBox("Hello Fedor")
end

Конечно, это просто пример, есть множество других сообщений и сценариев.

Ведение лога

Можно также включить режим дебаггера, отредактировав coreconfig.xml в папке GTA\MTA. Найдите в нем следующий тэг:

<debugfile/>

И впишите туда адрес будущего лог-файла (путь указывайте, начиная с папки GTA):

<debugfile>MTA\debugscript.log</debugfile>

И теперь все сообщения дебаггера будут записываться в этот файл. Если хотите выключить ведение лога, оставьте содержимое тэга пустым:

<debugfile/>

Стратегии отладки

Есть несколько методов, помогающих в поиске ошибок, кроме проверки всего кода. Большинство из них включает вывод различной информации, зависящей от конкретной ситуации.

Полезные функции

Сперва несколько функций, которые можно использовать для отладки.

  • outputDebugString или outputChatBox для вывода информации
  • tostring() для преобразования значений переменных в строки, к примеру когда они содержат булевы значения
  • getElementType для определения типа элемента MTA

Вывод отладочных сообщений для проверки как, когда или как часто выполняется та или иная часть кода

Простейшим примером является проверка, выполняется ли условие в операторе if. Чтобы выполнить такую проверку, просто добавьте любое сообщение (которое вы сможете узнать впоследствии) внутрь секции, выполняемой при истинном условии в операторе if.

if (variable1 == variable2) then
	outputDebugString("entered if")
	-- do anything
end
Warning.png This page requires local translation. If page will remain not translated in reasonable period of time it would be deleted.
After translating the page completely, please remove the ‎{{translate}}‎ tag from the page.

Another application would be to check when variable values are modified. First search for all occurences of the variable being edited and add a message just beside i

Вывод отладочных сообщений для проверки значения переменной

Допустим, вы хотите создать маркер, но он не появляется в ожидаемом месте. Первое, что вы возможно захотите сделать - проверить, выполняется ли функция createMarker.Но одновременно с этим можно также проверить передаваемые а функцию createMarker параметры.

outputChatBox(tostring(x).." "..tostring(y).." "..tostring(z))
createMarker(x,y,z)

Этот код выведет 3 переменные - координаты маркера. Можно легко сравнить предполагаемые значения со значениями, выведенными при отладке. tostring() преобразует переменные в строки, даже если они имеют, например, логический тип.

Warning.png This page requires local translation. If page will remain not translated in reasonable period of time it would be deleted.
After translating the page completely, please remove the ‎{{translate}}‎ tag from the page.

Пример

Imagine you created a colshape (collision shape) somewhere and you want a player to stay 10 seconds in it, then perform some action.

function colShapeHit(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)
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

When a player enters the colshape, debugscript outputs the following message:

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.

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

Now we still get a warning when a player enters the colshape, waits for the message and leaves it again:

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 a little help function:

function isTimer(timer)
	local timers = getTimers()
	for k,v in ipairs(timers) do
		if (v == timer) then
			return true
		end
	end
	return false
end

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)

function isTimer(timer)
	local timers = getTimers()
	for k,v in ipairs(timers) do
		if (v == timer) then
			return true
		end
	end
	return false
end