Debugging
From SA-MP Wiki
This is a quick guide to what debugging is and how to do it.
Contents |
Introduction
What is debugging?
Debugging is the process of adding temporary (or sometimes permanent) messages into code to give a visual representation of the script's process.
When can debugging be useful?
Errors in code can cause the code to stop at the error and skip the rest of the code, or even crash or freeze the server. These errors aren't picked up by the compiler, as they aren't errors when the script is compiled. An example of an error can be found below.
It can be hard to identify and locate an error in big scripts, possibly taking months. This is where debugging becomes extremely useful.
Example
In this example, the code won't cause the server to freeze/crash, but will cause the code to stop at the error, leaving the rest of the code un-processed.
new pKillStreak[MAX_PLAYERS]; // The player's current killstreak new pKillStreakReward[MAX_PLAYERS][5]; // 5 killstreaks can be stored per player public OnPlayerDeath(playerid, killerid, reason) { if(killerid != INVALID_PLAYER_ID) // If it wasn't suicide, handle the kill { new string[128], pName[MAX_PLAYER_NAME], kName[MAX_PLAYER_NAME]; GetPlayerName(playerid, pName, MAX_PLAYER_NAME); GetPlayerName(killerid, kName, MAX_PLAYER_NAME); format(string, sizeof(string), "%s (%i) was killed by %s (%i). Reason: %i.", pName, playerid, kName, killerid, reason); SendClientMessageToAll(COLOR_DARKRED, string); pKillStreak[killerid]++; // Add one to the killstreak if(pKillStreak[killerid] == 7) // 7 streak - Give attack helicopter { pKillStreakReward[killerid][pKillStreak[killerid]]++; // Add one to "attack helicopter" count (slot 7) SendClientMessage(killerid, COLOR_GREEN, "[7 KILLSTREAK] - Attack helicopter ready for deployment."); } SetPlayerScore(killerid, GetPlayerScore(killerid)+pKillStreak[killerid]); // If they had a 5 killstreak, they'd get 5 score. GivePlayerMoney(killerid, 1000*pKillStreak[killerid]); // If they had a 5 killstreak they'd get $5000 } return 1; }
Now this isn't the longest piece of code ever written, it's kept short to avoid confusion. Running that code, and allowing a player to get a 7 killstreak will stop the code at the error (which will be told below). Due to that, the code at the bottom that gives the score and cash won't be executed.
Adding the debug messages
Debug messages are most commonly either added using print() (or printf()) or client messages. For simplicity, we will use print(). To add debugging messages, you need to place print() messages every few lines between your code, and then run the code. The last print() to appear in the server_log will help you to locate the code which is causing the problem by looking at the code between said print and the next one.
This is the code with debugging added:
new pKillStreak[MAX_PLAYERS]; new pKillStreakReward[MAX_PLAYERS][5]; public OnPlayerDeath(playerid, killerid, reason) { printf("OnPlayerDeath(%i, %i, %i)", playerid, killerid, reason); if(killerid != INVALID_PLAYER_ID) { print("OnPlayerDeath: killerid valid"); new string[128], pName[MAX_PLAYER_NAME], kName[MAX_PLAYER_NAME]; GetPlayerName(playerid, pName, MAX_PLAYER_NAME); GetPlayerName(killerid, kName, MAX_PLAYER_NAME); print("OnPlayerDeath: names stored"); format(string, sizeof(string), "%s (%i) was killed by %s (%i). Reason: %i.", pName, playerid, kName, killerid, reason); SendClientMessageToAll(COLOR_DARKRED, string); print("OnPlayerDeath: message sent"); pKillStreak[killerid]++; printf("OnPlayerDeath: pKillStreak is now %i", pKillStreak[killerid]); if(pKillStreak[killerid] == 7) // Attack helicopter { print("OnPlayerDeath: 7 killstreak - give attack heli"); pKillStreakReward[killerid][pKillStreak[killerid]]++; // Add one to the count for the current killstreak (for multiple attack helicopters stored) print("OnPlayerDeath: 7 killstreak - added one to pKillStreakReward for attack heli"); SendClientMessage(killerid, COLOR_GREEN, "[7 KILLSTREAK] - Attack helicopter ready for deployment."); print("OnPlayerDeath: end of attack heli"); } print("OnPlayerDeath: Giving score..."); GivePlayerScore(killerid, pKillStreak[killerid]); // If they have a 5 killstreak, give them 5 score. print("OnPlayerDeath: Score given. Giving cash..."); GivePlayerMoney(killerid, 1000*pKillStreak[killerid]); // If they have a 5 killstreak give them $5000 print("OnPlayerDeath: Cash given."); } print("OnPlayerDeath: FINISHED"); return 1; }
As you can see, there are print functions used throughout the code. When the code is executed, it will print the debugging messages to the server console (and server_log.txt). You can then locate the problem.
server_log.txt would display this when a player gets a 7 kill-streak:
[timestamp] OnPlayerDeath(1, 2, 3) [timestamp] OnPlayerDeath: killerid valid [timestamp] OnPlayerDeath: names stored [timestamp] OnPlayerDeath: message sent [timestamp] OnPlayerDeath: pKillStreak is now 7 [timestamp] OnPlayerDeath: 7 killstreak - give attack heli
But there's nothing after that?! What happened? The script stopped. Lets take a look at the code that is after the final print ('OnPlayerDeath: 7 killstreak - give attack heli'):
if(pKillStreak[killerid] == 7) // Attack helicopter { print("OnPlayerDeath: 7 killstreak - give attack heli"); pKillStreakReward[killerid][pKillStreak[killerid]]++; // <<<<<< THIS IS THE PROBLEM print("OnPlayerDeath: 7 killstreak - added one to pKillStreakReward for attack heli"); SendClientMessage(killerid, COLOR_GREEN, "[7 KILLSTREAK] - Attack helicopter ready for deployment."); print("OnPlayerDeath: end of attack heli"); }
There is only one line of code between the final print that was found in server_log.txt and the next, which is
pKillStreakReward[killerid][pKillStreak[killerid]]++;
This is the problem. The pKillStreakReward only has 5 cells, but we're trying to set the 8th, as pKillStreak[killerid] is 7 (arrays start at 0, so 7 is the 8th). This is what is happening:
pKillStreakReward[killerid][7]++; // Add one to "attack helicopter" count
What's next?
After you have located the problem, you must use your own knowledge to fix it (or ask for assistance on the SA:MP forums).
'Major Debugging'
If your server is crashing at random times, you will need to debug your entire script. While this may seem daunting at first, once you know what to do it's really not. It's either caused by a callback or a timer. What you need to do is place ONE print() on the first line of each callback, like so:
public OnPlayerDeath(playerid, killerid, reason) { print("DEBUG: OnPlayerDeath"); // code // code // code // code // code // code // code // code // code // code return 1; }
Then, when the server crashes simply look at the logs and see which callback was called last. Which ever is the last print printed, is the callback which contains the error. You can then debug that callback further, as in the first example.
This is similar to what you can expect to see in server_log.txt:
[16:49:54] OnPlayerStreamIn [16:49:54] OnPlayerStateChange [16:49:54] OnPlayerRequestClass [16:49:54] OnPlayerRequestClass [16:49:54] OnPlayerStateChange [16:49:54] OnPlayerSpawn [16:49:54] OnPlayerDeath
That indicated that some code in OnPlayerDeath caused the server to crash.
In some instances, you may want to print the callback's parameters, for this, printf can be used like so:
public OnPlayerDeath(playerid, killerid, reason) { printf("DEBUG: OnPlayerDeath(%i, %i, %i)", playerid, killerid, reason); // code // code // code // code // code // code // code // code // code // code return 1; }
Infinite Loops
Another common source of server freezes is infinite loops. while() can cause a never-ending, infinite loop of code that will stop the server from processing any more instructions. Here is an example of an infinite while() loop:
new var = 5; // Create a variable and set it to 5 while(var != 3) // If it's not 3 { var++; // Add one to it }
This will cause an infinite loop, because the variable will never get set to 3!
Client Messages
Throughout this tutorial the print and printf functions have been used, which prints text to the server console/logs. This isn't always the most useful function to use, as you would have to go in-game, test the code, then minimize to check the console/logs.
This is where SendClientMessage could be more useful, or perhaps even textdraws or game-text. You will need to use format, unlike printf where you can specify the variables after the string.
For example, this code will constantly display a variable for the player in OnPlayerUpdate, so you can see if it gets changed accidentally:
new pVariable[MAX_PLAYERS]; // Just used for this example public OnPlayerUpdate(playerid) { new debugStr[32]; format(debugStr, sizeof(debugStr), "~W~pVariable: ~Y~%i", pVariable[playerid]); GameTextForPlayer(playreid, debugStr, 3000, 3); // Use style 3 always, the rest aren't great for this. Style 5 will refuse to show after a few seconds }
Or if you wanted to know what a player's variable was when they passed through a race checkpoint:
new pCP[MAX_PLAYERS]; // The current checkpoint the player can see in the race public OnPlayerEnterRaceCheckpoint(playerid, checkpointid) { pCP[playerid]++; new debugStr[18]; format(debugStr, sizeof(debugStr), "Checkpoint: %i", pCP[playerid]); // Show next checkpoint, handle finishline etc. return 1; }
Conclusion
This tutorial was made in a short period of time, without any sort of planning. While not essentially rushed, future improvements may be made if people find this tutorial useful.
Thank you for reading, I hope you learned something. If there are any problems with this tutorial or if any improvements could be made, don't hesitate to edit it! Written by Mike, 16/11/2011. Last updated: 5th December 2011 by Mike
Share this in the scripting discussion section of the SA:MP forums when scripters ask 'why does my script crash'!