Depuração
É comum ao programar cometer erros de sintaxe e semântica. O primeiro deles acontece porque o computador não tem a sensibilidade do ser humano para compreender um texto, mesmo que tenha erros de ortografia. Já o segundo, é basicamente culpa sua, não do computador. Por exemplo, em um complexo algoritmo para distribuir jogadores pelo mapa, ocorre uma divisão por zero. De fato, até este ponto, a máquina só seguiu suas instruções, que eventualmente, em um caso específico chegou a uma operação matemática inválida.
A depuração (debug, em inglês) ajuda o programador a encontrar erros apontando o que são eles são e onde estão, poupando seu valioso tempo. Esta página irá ensinar como ativar a depuração e dar alguns exemplos de como corrigir erros comuns.
Contents
Debug no Console
O MTA vem com um sistema no console onde mensagens de debug relacionadas às funções e scripts são mostradas. Lembrando que é necessário ter uma permissão de administrador para ativá-lo (a não ser se você modificar o ACL). Para fazer isso, digite debugscript X
no console, em que X é o nível:
- 1: mostra somente os erros
- 2: erros e avisos
- 3: erros, avisos e mensagens informativas
Ao digitar debugscript 3
, todas as ocorrências serão mostradas. O nível 3 ou o 2 são os recomendados para a maioria dos casos. É recomendado ativar o debugscript' para testar seu script e detectar os erros de digitação.
Exemplo
Neste código há dois erros. Consegue encontra-los?
function SayHello(message, player) if (getPlayerName(player) == "Cebola") outputChatbox("Olá, Cebola!") end end addEventHandler("onChatMessage", root, SayHello)
Quando este script for carregado, no console irá aparecer algo assim:
- INFO: Loading script failed: meuRecurso\script.lua:2: 'then' expected near ´outputChatbox'
Isso significa que o script não pode ser executado porque há um erro de sintaxe. Nesta mensagem, primeiramente aparece qual script de qual recurso deu problema. Depois disso, há dois pontos e um número mostrando a linha onde está o erro; para facilitar o trabalho do programador quando se trata de scripts maiores. Depois disso, a mensagem irá variar de acordo com o tipo de erro. Neste caso, é fácil concluir que o erro está localizado no meuRecurso e na linha dois do arquivo script.lua. Desta vez está na cara: precisamos colocar o "then" na cláusula if.
function SayHello(message, player) if (getPlayerName(player) == "Cebola") then outputChatbox("Olá, Cebola!") end end addEventHandler("onChatMessage", root, SayHello)
Agora tudo será carregado com sucesso e nenhum erro aparecerá até que um jogador apelidado "Cebola" dizer algo no chat. Aí no console irá aparecer:
- ERROR: myResource\script.lua:2: attempt to call global 'outputChatbox' (a nil value)
Isso significa que a função outputChatbox é um valor nil, ou seja, ele não existe! Isso porque a função na verdade se chama outputChatBox, e não outputChatbox. Então, tome cuidado com a letra maiúscula B.:
function SayHello(message, player) if (getPlayerName(player) == "Cebola") then outputChatBox("Olá, Cebola!") end end addEventHandler("onChatMessage", root, SayHello)
Lembrando que isso é só um exemplo e não será sempre desse jeito, pois há vários tipos de erros que podem acontecer.
Log de debug do Cliente e Servidor
Servidor
Na pasta ../server/mods/deathmatch, haverá dois arquivos com funções parecidas:
- local.conf - são as configurações oriundas do item "host game" no menu principal do MTA. Isto é uma maneira rápida de iniciar um servidor de testes dentro do próprio programa. Quando o usuário fecha o jogo, ele é automaticamente desligado também.
- mtaserver.conf - é usado quando se inicia o "MTA Server.exe" dentro da pasta ../server/. Em outras palavras, o servidor é rodado em prompt de comando e não necessita de o MTA estar executando junto. Isso é muito útil para os interessados em montar um servidor dedicado ou mesmo experimentar como é mexer em um servidor de verdade.
Dependendo das suas necessidades, com certeza irá querer alterar essas configurações abaixo:
<!-- Indica o nome e a localização do arquivo usado pelo debugscript. Se for deixado em branco, o servidor não o salvará. --> <scriptdebuglogfile>logs/scripts.log</scriptdebuglogfile> <!-- Indica o nível de debugscript para este arquivo. Todos os níveis 0,1,2,3 estão disponíveis. Quando não especificado, zero é o padrão. --> <scriptdebugloglevel>0</scriptdebugloglevel>
Como as descrições acima já dizem, é opcional salvar um arquivo de log de acordo com o nível desejado. Os três níveis estão descritos acima, sendo neste caso o zero como a não ativação do debugscript - fazendo o arquivo ficar em branco. Se 3 for ativado, todos os erros encontrados no lado do servidor serão salvos em: ../server/mods/deathmatch/logs/scripts.log
Cliente
Todos os erros do cliente serão salvos na pasta ../server/clientscript.log. Isso já é definido por padrão, então nenhuma configuração é necessária.
Habilidades em Deubug
Em vez de ficar procurando no código os erros cometidos, há algumas coisas a serem feitas para ajudar a encontrar-los. A maioria delas estão relacionadas com a adição de alguma mensagem de debug informando o que está ocorrendo no script.
Funções Úteis
As funções abaixo podem lhe ajudar com o debug:
- outputDebugString ou outputChatBox para escrever qualquer informação na tela (de preferência outputDebugString para suporte técnico)
- tostring() para tornar qualquer variável em uma string. Muito útil para saber o que ela é, escrevendo na tela. Não será necessário usa-la se a variável já uma string ou um número.
- getElementType verifica o tipo de elemento do MTA em questão.
- isElement verifica se elemento existe.
Adicionando mensagens de debug para verificar se, quando ou por qual frequência um código é executado
Se você terminou de escrever um código e percebe que algo esperado não acontece e fica em dúvida se as instruções foram executadas ou não; nesse caso é recomendado adicionar mensagens de debug para verificar os passos.
Um exemplo típico seria checar se uma clausula if foi atendida ou não:
if (variable1 == variable2) then outputDebugString("A variável 1 é igual a 2.") -- portanto, faça alguma coisa end
Outra maneira semelhante seria verificar se alguma variável muda. Em outras palavras, é só adicionar uma mensagem de debug depois de cada vez que isso acontece.
Adicionando uma mensagem de debug para verificar o valor de uma variável
Digamos que desejas criar um marcador (marker), mas ele não aparece na posição esperada. A primeira coisa a ser feita é verificar se a função createMarker foi executada com sucesso. Depois disso, só precisamos verificar os valores usados nela.
local marker = createMarker(x,y,z) if not marker then outputChatBox("O marcador não pode ser criado.") end outputChatBox("A posX é "..x..", posY "..y..", e posZ "..z)
Feito isso, as variáveis usadas como coordenadas serão mostradas. Lembrando que se uma variável não for uma frase ou número, é recomendado usar a função tostring(), pois ela torna qualquer variável agrupável em uma string, mesmo se for uma bool ou tabela.
Exemplo
Imagine que você criou um detector de colisão e se o jogador ficar dez segundos dentro dele, algo vai acontecer:
function colShapeHit(player) -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit", root, colShapeHit) function colShapeLeave(player) -- destrua o timer quando o jogador sair do colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave", root, colShapeLeave)
Quando o jogador entrar no colshape, o debugscript irá escrever a seguinte mensagem:
- ERROR: ..[path]: attempt to index global 'colshapeTimer' (a nil value)
Isso significa que você tentou endereçar uma tabela não existente quando tentou colocar o timer dentro dela; ou seja, ela é um valor nil - portanto, não existe. Para corrigir isso, precisamos verificar se a tabela não existe, e caso a resposta for positiva; a criaremos.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit", root, colShapeHit) function colShapeLeave(player) -- destrua o timer quando o jogador sair do colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",root,colShapeLeave)
Desta vez receberemos um aviso quando o jogador entrar no colshape, esperar a mensagem e sair de lá:
- WARNING: [..]: Bad argument @ 'killTimer' Line: ..
Tirando isso (vamos corrigir mais tarde!), tudo parece funcionar bem. O jogador entra no colshape e o timer começa a contar: se ele esperar, a mensagem é escrita, caso contrário o timer é destruído. Mas...
Um erro imperceptível
Por alguma razão, quando o jogador entra no colshape usando um veículo, a mensagem é escrita duas vezes; como se o código tivesse sido executado duas vezes. Então, adicionamos uma mensagem de debug para verificar isso.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- adicionamos uma mensagem de debug outputDebugString("colShapeHit") -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- adicionamos outra mensagem de debug outputDebugString("colShapeLeave") -- destrua o timer quando o jogador sair do colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Agora dá pra ter certeza que as funções são executadas duas vezes quando entramos com um veículo, mas só uma quando se está a pé. É possível que o veículo seja um elemento a parte e ativa a função de forma independente ao jogador. Para confirmar essa teoria, verificamos se a variável player realmente se refere ao jogador.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- adicionamos uma mensagem de debug, seguido do elemento "jogador" outputDebugString("colShapeHit"..getElementType(player)) -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- adicionamos outra mensagem de debug, incluindo o jogador outputDebugString("colShapeLeave "..getElementType(player)) -- destrua o timer quando o jogador sair do colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
A mensagem de debug nos diz que uma referência da variável player é realmente o jogador, mas outra é o veículo. Já que nós destinamos o evento especificamente ao boneco dele, adicionaremos uma cláusula if, obrigando o término da execução do código caso a variável não for o jogador.
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- adicionamos um verificador if (getElementType(player) ~= "player") then return end -- adicionamos uma mensagem de debug, seguido do elemento "jogador" outputDebugString("colShapeHit"..getElementType(player)) -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- adicionamos ele novamente if (getElementType(player) ~= "player") then return end -- adicionamos outra mensagem de debug, incluindo o jogador outputDebugString("colShapeLeave "..getElementType(player)) -- destrua o timer quando o jogador sair do colshape killTimer(colshapeTimer[player]) end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Agora tudo está funcionando como o planejado, mas aquela mensagem de aviso mencionada acima ainda aparece. Isso porque quando tentamos destruir o timer depois que o jogador saí do colshape, ele não existe mais, já que os 10 segundos se passaram. Em outras palavras, o timer é automaticamente destruído quando ele conta os 10 segundos. Há diversas formas de se livrar do aviso, uma vez que temos conhecimento da não existência do timer. Uma delas seria verificar se ele existe na tabela. Para fazer isto, usamos a função isTimer na hora de destruí-lo:
if (isTimer(colshapeTimer[player])) then killTimer(colshapeTimer[player]) end
Então o código completo ficará assim:
function colShapeHit(player) if (colshapeTimer == nil) then colshapeTimer = {} end -- adicionamos um verificador if (getElementType(player) ~= "player") then return end -- adicionamos uma mensagem de debug, seguido do elemento "jogador" outputDebugString("colShapeHit"..getElementType(player)) -- criamos um timer para escrever uma mensagem (pode também ativar uma função) -- e inserimos ele em uma tabela, usando o jogador como index colshapeTimer[player] = setTimer(outputChatBox,10000,1,"O jogador permaneceu dez segundos dentro do colshape!") end addEventHandler("onColShapeHit",getRootElement(),colShapeHit) function colShapeLeave(player) -- adicionamos ele novamente if (getElementType(player) ~= "player") then return end -- adicionamos outra mensagem de debug, incluindo o jogador outputDebugString("colShapeLeave "..getElementType(player)) -- destrua o timer quando o jogador sair do colshape if (isTimer(colshapeTimer[player])) then killTimer(colshapeTimer[player]) end end addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)
Verificando problemas na performance
Se o seu servidor estiver trabalhando muito além da conta ou você deseja se certificar que seus scripts são eficientes, é possível matar o problema pela raiz usando uma boa ferramenta inclusa na instalação: performancebrowser. É só inicia-lo usando 'start performancebrowser'. Caso ele não exista, pode encontrá-lo no último pacote de recursos hospedado no GitHub. Esta ferramenta inclui uma série de informações relativas a performance, como:
- Memory leaks - uso excessivo de memória
- Element leaks - criação excessiva de elementos
- CPU usage - uso excessivo de CPU
Para acessar o performancebrowser, é preciso ir até o navegador e digitar o endereço: http://<ip-do-servidor>:<porta>/performancebrowser/. Um exemplo seria o endereço do servidor local: http://127.0.0.1:22005/performancebrowser/. Lembrando que é preciso:
- colocar a barra (/) no final do endereço
- logar no jogo com uma conta de administrador ou outra que tenha acesso no ACL a:
- resource.performancebrowser.http e resource.ajax.http
Vale Lembrar: A maioria das informações relevantes estão nas categorias Lua timing e Lua memory. E, o problema está nos valores mais altos apresentados. |
Exemplos de scripts problemáticos
Um deles seria adicionar dados a alguma tabela, mas não remover posteriormente. Porém, isso levaria alguns meses ou até anos para danificar o funcionamento do servidor.
local someData = {} function storeData() someData[source] = true -- não há nenhum evento para retirar os dados quando o jogador sair. Isso é considerado memory leak -- Entrando na aba Lua timing, você pode verificar o uso de RAM de cada recurso. end addEventHandler("onPlayerJoin", root, storeData)
Um caso possível de Element leak é criar colisores temporários e nunca destruí-los mais tarde. Isso aumenta muito o uso de transferência dados (bandwidth), CPU e memória pelo servidor.
function useTemporaryCol() local col = createColCircle(some code here) if (algo que deveria acontecer...) then destroyElement(col) end -- E as vezes não acontece, fazendo o colisor permanecer no mapa pra sempre... -- Criando de centenas à milhares de coliores sem função alguma. -- Na aba Lua timing é possível ver quantos elementos cada script criou. end
O alto uso de CPU resulta na queda de FPS, fazendo o servidor um lugar ruim para se jogar. Em 24 horas, isso pode causar sérios prejuízos. O número de elementos criados detectados na aba Lua timing não ajudará neste caso, mas o Lua memory sim.
addEventHandler("onPlayerJoin", root, function() -- [Código para o novo jogador] -- Até que um evento é adicionado para quando ele sair addEventHandler("onPlayerQuit", root, function() -- Encontrou o problema? Esse evento além de usar o root como o seu causador, é criado infinitamente... end) end)
Uma função usa muito o CPU quando ela demora para terminar de ser executada. O exemplo abaixo é uma delas. Sem o performancebrowser, você não perceberia tão cedo que esta é a causa do problema. Mas com ele, verá na aba Lua timing que o seu recurso usa muito o potencial do CPU. Ficará mais fácil descobrir isso se usar o parâmetro 'd' na caixa de entrada Options. Isso porque ela lhe dirá também a linha em que está o código problemático.
function umaFuncaoPoucoEficaz() -- para cada cem mil coisas quaisquer for i=1, 100000 do -- [código abaixo] end end