Functions
#1

Some questions that could be discussed in this thread:

When should we use a function?
How should we use a function?
What parameters should the function have? Which should be optional?
When should a function be stock?
When should a function be public?



My views:

Functions (also called 'subroutines') are useful when you want to execute same code many times, only with some small differences each time. One way to solve this problem would be using copy-paste and then change the parts that need to be changed. Another (better) method is to construct a function.

The famous Godfather edits are good examples of scripts that don't use functions very much (or use them inefficiently).

Example taken from a Godfather edit /family command (also known as /f and /faction):

pawn Code:
new leader = PlayerInfo[playerid][pLeader];
new member = PlayerInfo[playerid][pMember];
if(member==1)
{
    if(PlayerInfo[playerid][pRank] == 8) { format(string, sizeof(string), "** (( Chief %s: %s )) **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 7) { format(string, sizeof(string), "** (( Deputy Chief %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 6) { format(string, sizeof(string), "** (( Captain %s: %s )) **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 5) { format(string, sizeof(string), "** (( Lieutenant %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 4) { format(string, sizeof(string), "** (( Sergeant %s: %s )) **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 3) { format(string, sizeof(string), "** (( Corporal %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 2) { format(string, sizeof(string), "** (( Police Officer %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 1) { format(string, sizeof(string), "** (( Cadet %s: %s ))  **", sendername, result); }
    else { format(string, sizeof(string), "** (( Cadet %s: %s )) **", sendername, result); }
    SendFamilyMessage(PlayerInfo[playerid][pMember], 0x7BDDA5AA, string);
}
if(member==2)
{
    if(PlayerInfo[playerid][pRank] == 6) { format(string, sizeof(string), "** (( Director %s: %s )) **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 5) { format(string, sizeof(string), "** (( Assistant Director in Charge %s: %s )) **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 4) { format(string, sizeof(string), "** (( Special Agent in Charge %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 3) { format(string, sizeof(string), "** (( Special Agent %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 2) { format(string, sizeof(string), "** (( Special Agent Trainee %s: %s ))  **", sendername, result); }
    else if(PlayerInfo[playerid][pRank] == 1) { format(string, sizeof(string), "** (( Professional Staff %s: %s ))  **", sendername, result); }
    else { format(string, sizeof(string), "** (( Professional Staff %s: %s )) **", sendername, result); }
    SendFamilyMessage(PlayerInfo[playerid][pMember], 0x7BDDA5AA, string);
}
Similar pattern continues until 'member' reaches 16.

It does work, but is this the best way to do it? As you can see, the lines hardly differ. Only things that change are rank id and rank name.

And there are other commands that need to display the same ranks. What if we want to change the name of a rank? We would have to find ALL the places in the script where it occurs and change them.

'format' is used 16 times, 'SendFamilyMessage' 2 times. There must be a way to make it work with only 1 'format' and only 1 'SendFamilyMessage'. This is the point where a function should be made which decides what rank name should be displayed.

Function planning:

1) the function of the function (what should the function do?)

It should return a string with the rank name of a family

2) the name of the function

It should be descriptive enough, so you can get the main idea of what the function does just by looking at the name of it. Let's call ours 'GetFamilyRankName'

3) parameters (what should be the input?)

Rank name depends on family id and rank id, so we need to input them as parameters

pawn Code:
GetFamilyRankName(familyid, rankid)
4) writing the function

pawn Code:
GetFamilyRankName(familyid, rankid)
{
    new name[32];
    switch(familyid)
    {
        case 1:
        {
            switch(rankid)
            {
                case 1: name = "Cadet";
                case 2: name = "Police Officer";
                case 3: name = "Corporal";
                case 4: name = "Sergeant";
                case 5: name = "Lieutenant";
                case 6: name = "Captain";
                case 7: name = "Deputy Chief";
                case 8: name = "Chief";
                default: name = "Cadet";
            }
        }
        case 2:
        {
            switch(rankid)
            {
                case 1: name = "Professional Staff";
                case 2: name = "Special Agent Trainee";
                case 3: name = "Special Agent";
                case 4: name = "Special Agent in Charge";
                case 5: name = "Assistant Director in Charge";
                case 6: name = "Director";
                default: name = "Professional Staff";
            }
        }
    }
    return name;
}
After creating the GetFamilyRankName function, our example will turn into this

pawn Code:
new member = PlayerInfo[playerid][pMember];
new rank = PlayerInfo[playerid][pRank];
format(string, sizeof(string), "** (( %s %s: %s )) **", GetFamilyRankName(member, rank), sendername, result);
SendFamilyMessage(member, 0x7BDDA5AA, string);
with only 1 format and 1 SendFamilyMessage used.

Now, if you want to change a rank or add a rank, all you have to do is make the needed changes in GetFamilyRankName and that's it! You don't need to even touch the commands that use rank names.


***

If you are making a function that does something with a string that is passed as a parameter, you might need to know the size of that string. For example when using strins in it. Compiling something like this

pawn Code:
InsertSomething(abc[], pos)
{
    strins(abc, "insert this", pos);
}
will give

Code:
warning 224: indeterminate array size in "sizeof" expression (symbol "maxlength")
because the last parameter of strins (maxlength) is by default 'sizeof string'. But compiler doesn't know the size of 'abc' at this point. To fix this problem, you can use the same method that strins uses: silently pass the string size as an optional parameter

pawn Code:
InsertSomething(abc[], pos, maxlength=sizeof abc)
{
    strins(abc, "insert this", pos, maxlength);
}
Then you can still call the function like this

pawn Code:
InsertSomething(text, 5);
without needing to worry about the size of the string.


***

When should a function be stock?

I add 'stock' in front of functions that are independent. By that I mean they can be used in any other script without the need to modify them

Examples of stocks

pawn Code:
stock IsPlayerSpawned(playerid)
{
    switch(GetPlayerState(playerid))
    {
        case 1,2,3: return 1;
    }
    return 0;
}
pawn Code:
stock GetVehicleDriver(vehicleid)
{
    for(new i=0; i < MAX_PLAYERS; i++)
    {
        if(IsPlayerConnected(i) && GetPlayerVehicleID(i) == vehicleid)
        {
            if(GetPlayerState(i) == PLAYER_STATE_DRIVER)
            {
                return i;
            }
        }
    }
    return INVALID_PLAYER_ID;
}

When should a function be public?

Only when it's called ...

1) ... by a timer (SetTimer/SetTimerEx)
2) ... from another script (CallRemoteFunction)
3) ... from the server (callbacks)

If the function doesn't fall into one of these categories, it doesn't need to be public.




This section of the forums is for discussing and so is this thread. You can add new points, explain your own views/scripting habits, add new questions to be discussed - anything related to (PAWN) functions.
Reply
#2

A function is not the same as a subrutine. The main difference is that a sub rutine just execute code in a certain order, while a function do stuff with the parameters passed and may return a value, an other big difference between them is that a function should not change values outside the function itself while a subrutine may change some external values.
Reply
#3

"It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure. Basic professional ethics would instead require him to write a DestroyCity procedure, to which Baghdad could be given as a parameter." - Nathaniel Borenstein

Now, for the rank example: personally, I use constant arrays for those. Of course they don't need to be constants, but I do not intend on changing these via the script so they might as well be. Now, whenever I want to retrieve the rank, I simply pass the player's value as the index parameter (if that makes sense) without having to resort to large switch statements;

pawn Code:
new const stock ePoliceLevels[11][] = {
    {"Cadet"},                  // 0
    {"Officer In Training"},    // 1
    {"Officer"},                // 2
    {"Senior Officer"},         // 3
    {"Sergeant"},               // 4
    {"Lieutenant"},             // 5
    {"Captain"},                // 6
    {"Inspector"},              // 7
    {"Commandant"},             // 8
    {"Chief"},                  // 9
    {"Commissioner"}            // 10
};

PlayerStats[cop][pLevel]++;

format(cString, sizeof(cString), "  Your rank will be increased to %s.",
    ePoliceLevels[PlayerStats[cop][pLevel]]);
SendClientMessage(cop, COLOR_LIGHTBLUE, cString);
Of course this is not the entire code and additional checks are in place here to guard against OOB errors.
Reply
#4

Quote:
Originally Posted by the_chaoz
View Post
A function is not the same as a subrutine. The main difference is that a sub rutine just execute code in a certain order, while a function do stuff with the parameters passed and may return a value, an other big difference between them is that a function should not change values outside the function itself while a subrutine may change some external values.
Well Wikipedia sees them as one - http://en.wikipedia.org/wiki/Functio...ter_science%29
Reply
#5

Ty , i hardy able to create differnce b/w a stock function and a simepel function [excluding callbacks]
this helped me clarifying a bit :P
Reply
#6

When I scripted my own FS, I noticed that this kind of functions are very useful, so even if I already know that, nice job, it will help more than one scripter
Reply
#7

Hm, this is going to be useful and helpful for me if I wanted to make the admins show with their ranks, thank you
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)