Thread: Tips & Tricks
View Single Post
Old 26/01/2011, 08:48 AM   #1
Slice
High-roller
 
Join Date: Mar 2008
Location: Sweden
Posts: 1,836
Reputation: 1607
Spray Tips & Tricks

hey,
I thought I'd share a couple things that I use/find useful a lot when it comes to SA-MP scripting.

I'll probably add a bunch of stuff to this topic. If you have anything to contribute, let me know!

Current topics I'll be discussing:
  • The ternary operator
  • Simple int -> bool conversion
  • Fastest string loop
  • Fastest plain player-loop
  • Short functions
  • Multiple actions in one statement
  • Running code just after a function finishes
  • Getting rid of stupid tag warnings
  • "Char-arrays"
  • Split up numeric literals
  • Bit-flags in enums (advanced)
  • Using logical operators for tiny if-statements (advanced)
  • Efficient memory management with stock const (advanced)

The ternary operator
The ternary operator is an operator that takes 3 arguments; if the first argument is true then the argument right next to it will be used, if not then the last argument will be used.

The structure of the operator is this:
condition ? true : false

Here are a couple of examples:
pawn Code:
if ( a == b )
    c = d;
else
    c = e;
With the ternary operator:
pawn Code:
c = ( a == b ) ? d : e;
//   if-^   then-^   ^-else
A couple examples:
pawn Code:
SetPlayerColor( playerid, ( Team[ playerid ] == TEAM_ONE ) ? COLOR_RED : COLOR_BLUE );
// Set the color to red if the player is in TEAM_ONE, otherwise set it to blue.

GivePlayerWeapon( playerid, ( IsMadnessEnabled() ) ? WEAPON_MINIGUN : WEAPON_FLOWER, 5000 );
// If IsMadnessEnabled is true, give the player a minigun!

public OnPlayerSpawn( playerid )
{
    SetPlayerHealth( playerid, ( IsSuddenDeathEnabled() ) ? 1.0 : 100.0 );
   
    if ( ( IsPlayerAdmin( playerid ) ) ? SetPlayerPos( playerid, AdminSpawnX, AdminSpawnY, AdminSpawnZ ) : SetPlayerPos( playerid, PlayerSpawnX, PlayerSpawnY, PlayerSpawnZ ) ){}
    // I have to wrap this inside an if statement to avoid getting a warning from the PAWN compiler!
}

file = fopen( ( useSpecialFile ) ? ("special_file.txt") : ("normal_file.txt") );
// Strings need parentheses around them or the PAWN compiler will generate an error.

// You can also use ternary operators inside ternary operators!
file = fopen( ( useFile == 1 ) ? ("file1.txt") : ( ( useFile == 2 ) ? ("file2.txt") : ( ( useFile == 3 ) ? ("file3.txt") : ("file0.txt") ) ) );

// ..let's break that down
    new File:file = fopen(
        ( useFile == 1 ) ? ("file1.txt")
            : ( ( useFile == 2 ) ? ("file2.txt")
                : ( ( useFile == 3 ) ? ("file3.txt")
                    : ("file0.txt") ) )
    );
Simple int -> bool conversion
Sometimes you end up having to convert an int to a bool, the proper way to do that would be:
pawn Code:
new myInt = 50;
new bool:myBool = !!myInt;

This will make myBool become 0 if myInt equals 0, otherwise 1.
Fastest string loop
This is, by my experience, the fastest way to loop through a string.

pawn Code:
for ( new i, l = strlen( string ); i != l; i++ )
{
    // ..
}
Fastest plain player-loop
Note that foreach is faster than this - that's why the heading says plain player-loop.

pawn Code:
for ( new slots = GetMaxPlayers( ), i; i < slots; i++ )
{
    if ( !IsPlayerConnected( i ) )
        continue;
   
    // code for connected players
}
Short functions
If your function has only one statement - you can declare them like this:
pawn Code:
stock SomeFunction( someInput )
    return someArray[ someInput / 2 ];
Multiple actions in one statement
Statements in programming are what you'd call instructions - in PAWN these statements are all separated by the semicolon ( ; ). Sometimes you want to fit code on only one line for some reason, here's how you do that:
pawn Code:
stock KickEx( playerid, reason[] )
    SendClientMessage( playerid, 0xC00000FF, "You got kicked!! Reason:" ), SendClientMessage( playerid, 0xC00000FF, reason ), Kick( playerid );
// Sends the two client messages then kicks the player.

stock DoStuff( playerid )
    return DoFirstThing( playerid ), DoSecondThing( playerid ), DoThirdThing( playerid ), DoLastThing( playerid );
// DoStuff will return what DoLastThing returns.

public OnPlayerRequestSpawn( playerid )
{
    if ( !IsPlayerLoggedIn( playerid ) )
        return SendClientMessage( playerid, 0xC00000FF, "You're not logged in!" ), 0;
    // Send the client message and return 0
   
    return 1;
}
Running code just after a function finishes
This really isn't anything special, but I just thought I'd mention it as I haven't seen a lot of people do this.
I simply set a timer on 0 ms with no repeat somewhere and that function will be called almost right after the current function finished.

Why?
Sometimes you want to run code right after the current function finishes, here's a short example of a really handy function:
pawn Code:
stock DBResult:db_query_ex( DB:db, query[ ], bool:storeResult = true )
{
    new DBResult:dbrResult = db_query( db, query );
   
    if ( dbrResult )
    {
        if ( storeResult )
            SetTimerEx( "db_query_ex_free", 0, false, "i", _:dbrResult );
        else
            db_free_result( dbrResult );
    }
   
    return dbrResult;
}

forward db_query_ex_free( DBResult:dbrResult );
public  db_query_ex_free( DBResult:dbrResult )
    db_free_result( dbrResult );

// EXAMPLE:

public OnFilterScriptInit( )
{
    new DB:db, DBResult:dbrResult, buffer[ 16 ];
   
    db = db_open( "test.db" );
   
    dbrResult = db_query_ex( db, "SELECT 50" );
   
    db_get_field( dbrResult, 0, buffer, sizeof( buffer ) - 1 );
   
    print( buffer );
   
    // Even if the script would get some sort of error and abort running the current function,
    // the result will still get freed so you won't have a memory leak!
}

Using IsPlayerAdmin inside OnRconLoginAttempt doesn't work - the admin-status is set after that function executes. Example:
pawn Code:
new
    bool:g_IsRconAdmin[ MAX_PLAYERS ]
;

public OnPlayerConnect( playerid )
    g_IsRconAdmin[ playerid ] = false;

public OnRconLoginAttempt( ip[ ], password[ ], success ) // IsPlayerAdmin returns false if you check it inside this function. :(
    SetTimer( "CheckNewRconAdmins", 0, false );

forward CheckNewRconAdmins( );
public  CheckNewRconAdmins( )
{
    for ( new slots = GetMaxPlayers( ), playerid; playerid < slots; playerid++ )
    {
        if ( !g_IsRconAdmin[ playerid ] && IsPlayerAdmin( playerid ) )
        {
            // IsPlayerAdmin always returns false for unconnected players so we can save some performance by only calling that function.
           
            OnPlayerRconLogIn( playerid );
           
            break;
            // There should be at most new admin each function call, so we can break out of the loop now.
        }
    }
}

OnPlayerRconLogIn( playerid )
{
    SendClientMessage( playerid, 0x0000C0FF, "Welcome, Mr. Rcon!" );
}

Here's a part of a post by ****** explaining how he uses this:
Quote:
Originally Posted by ****** View Post
I find this useful to apply a large set of operations at once. If you look in the YSI library YSI_td.own it can dynamically update textdraws, so you can move them about the screen or change the colour etc. If you have code which looks like this:

pawn Code:
TD_Colour(td, 0xFF0000AA);
TD_SetShadow(td, 3);
TD_Font(td, 2);

That will change the textdraw for anyone looking at it to a red style 2 TD with a shadow, however because of the way the system used to work that would have redrawn the textdraw three times when it doesn't need to. The old method of fixing this was an extra parameter:

pawn Code:
TD_Colour(td, 0xFF0000AA, false);
TD_SetShadow(td, 3, false);
TD_Font(td, 2);

So only the last update in a set would change the appearance, the new system however uses a timer in much the same way as you just described. All the functions contain this (or something similar):

pawn Code:
if (YSI_g_sTimer[td] == -1)
{
    YSI_g_sTimer[td] = SetTimerEx("TD_Delay", 0, 0, "i", td);
}

That way the "TD_Delay" function is always called after the last current update is applied, without knowing a user's code in advance.
Getting rid of stupid tag warnings
When putting Text3Ds, DBResults, and stuff inside functions such as printf, format, SetTimerEx, CallLocalFunction, CallRemoteFunction you might notice you're getting a tag warning.
You're not doing anything wrong!
What you do to get rid of them is you clear the tag - clearing the tag is done by putting an underscore as a tag.
Example:
pawn Code:
new Text3D:t3dTest = Create3DTextLabel( .. ), Text:txTest = TextDrawCreate( .. );

printf( "DEBUG: %d, %d", _:t3dTest, _:txTest );
"Char-arrays"
PAWN has a feature for accessing single bytes in arrays, intended for use with packed strings. Most SA-MP natives doesn't cover packed strings, though.
You can, however, utilize these arrays a lot for memory optimization. A normal array can store values between -2,147,483,648 and 2,147,483,647; you don't always need that capacity, do you?
With packed strings you can store values between 0-255 (yes, no negative values; -1 will wrap to 255). When you know you don't need negative values and won't ever exceed 255, why not save some memory?

pawn Code:
new bool:g_IsPlayerSomething[ MAX_PLAYERS ]; // 500 cells * 4 bytes per cell = 2000 bytes!
new bool:g_IsPlayerSomething[ MAX_PLAYERS char ]; // 500 bytes = .. 500 bytes!

If you use "char arrays" instead of normal ones where needed 50 times you'll save 75,000 bytes (~73 kb).

Note!
When accessing these arrays, you need to use curly brackets (aka braces) as opposed to the normal square brackets.
Example:
pawn Code:
public OnPlayerConnect( playerid )
{
    g_IsPlayerSomething{ playerid } = false;
//                     ^          ^
}

public OnPlayerSpawn( playerid )
{
//                          v          v
    if ( g_IsPlayerSomething{ playerid } )
    {
        // ..
    }
}
Split up numeric literals

Quote:
Originally Posted by ****** View Post
A very small thing I found out the other day, you can split up long numeric literals in a similar way to how you do in maths. Normal writing:

Code:
345,234,148
Here "," is used as a thousands separator (sometimes "." I believe, but PAWN uses that for decimal). You can also do this in PAWN using "'" instead:

pawn Code:
345'234'148

And you can split HEX numbers up every 4, or binary numbers every 8:

pawn Code:
0x12FD'39C5
0b00000000'
11111111'01010101

As you can see, the highlighter doesn't like this.

Bit-flags in enums

Did you know that you can store 32 true/false values in one single variable? Not only do you save space, but you also get less clutter in your code.

You don't have to understand how the binary numeral system works; however, I recommend it. You can read more about it in this topic, if you're interested.

If you have, say, 100 true/false (bool) per-player variables you would use 195 KB of space. However, if you were to use 4 arrays with bit flags, only 8 KB of space would be used. The outcome would be exactly the same, but you would save 187 KB of space!

Here's an example also containing macros to simplify the usage.
pawn Code:
// Usage for all macros: BitFlag_X(variable, flag)
#define BitFlag_Get(%0,%1)            ((%0) & (%1))   // Returns zero (false) if the flag isn't set.
#define BitFlag_On(%0,%1)             ((%0) |= (%1))  // Turn on a flag.
#define BitFlag_Off(%0,%1)            ((%0) &= ~(%1)) // Turn off a flag.
#define BitFlag_Toggle(%0,%1)         ((%0) ^= (%1))  // Toggle a flag (swap true/false).

enum PlayerFlags:(<<= 1) {
    // It's important that you don't forget to put "= 1" on the first flag. If you don't, all flags will be 0.
   
    PLAYER_IS_LOGGED_IN = 1,   // 0b00000000000000000000000000000001
    PLAYER_HAS_GANG,           // 0b00000000000000000000000000000010
    PLAYER_CAN_BUY_PROPERTIES, // 0b00000000000000000000000000000100
    PLAYER_BLABLA_1,           // 0b00000000000000000000000000001000
    PLAYER_BLABLA_2,           // 0b00000000000000000000000000010000
    PLAYER_BLABLA_3,           // 0b00000000000000000000000000100000
    PLAYER_BLABLA_4,           // 0b00000000000000000000000001000000
    PLAYER_BLABLA_5,           // 0b00000000000000000000000010000000
    PLAYER_BLABLA_6,           // 0b00000000000000000000000100000000
    PLAYER_BLABLA_7,           // 0b00000000000000000000001000000000
    PLAYER_BLABLA_8,           // 0b00000000000000000000010000000000
    PLAYER_BLABLA_9,           // 0b00000000000000000000100000000000
    PLAYER_BLABLA_10,          // 0b00000000000000000001000000000000
    PLAYER_BLABLA_11,          // 0b00000000000000000010000000000000
    PLAYER_BLABLA_12,          // 0b00000000000000000100000000000000
    PLAYER_BLABLA_13,          // 0b00000000000000001000000000000000
    PLAYER_BLABLA_14,          // 0b00000000000000010000000000000000
    PLAYER_BLABLA_15,          // 0b00000000000000100000000000000000
    PLAYER_BLABLA_16,          // 0b00000000000001000000000000000000
    PLAYER_BLABLA_17,          // 0b00000000000010000000000000000000
    PLAYER_BLABLA_18,          // 0b00000000000100000000000000000000
    PLAYER_BLABLA_19,          // 0b00000000001000000000000000000000
    PLAYER_BLABLA_20,          // 0b00000000010000000000000000000000
    PLAYER_BLABLA_21,          // 0b00000000100000000000000000000000
    PLAYER_BLABLA_22           // 0b00000001000000000000000000000000
};

new
    // Create an array with the same tag as the enum
    PlayerFlags:g_PlayerFlags[MAX_PLAYERS]
;

public OnPlayerConnect(playerid) {
    // 0 - All flags are off (false). You must include the tag to prevent a warning.
    g_PlayerFlags[playerid] = PlayerFlags:0;
}

public OnPlayerLogIn(playerid) {
    BitFlag_On(g_PlayerFlags[playerid], PLAYER_IS_LOGGED_IN);
   
//  Without macros:
//  g_PlayerFlags[playerid] |= PLAYER_IS_LOGGED_IN;
}

public OnPlayerJoinGang(playerid) {
    BitFlag_On(g_PlayerFlags[playerid], PLAYER_HAS_GANG);
   
//  Without macros:
//  g_PlayerFlags[playerid] |= PLAYER_HAS_GANG;
}

public OnPlayerLeaveGang(playerid) {
    BitFlag_Off(g_PlayerFlags[playerid], PLAYER_HAS_GANG);
   
//  Without macros:
//  g_PlayerFlags[playerid] &= ~PLAYER_HAS_GANG;
}

public OnPlayerUpdate(playerid) {
    // DoSomething every-other player update.
   
    BitFlag_Toggle(g_PlayerFlags[playerid], PLAYER_BLABLA_19);
   
    if (BitFlag_Get(g_PlayerFlags[playerid], PLAYER_BLABLA_19)) {
        DoSomething();
    }
   
//  Without macros:
//  g_PlayerFlags[playerid] ^= PLAYER_BLABLA_19;
// 
//  if (g_PlayerFlags[playerid] & PLAYER_BLABLA_19) {
//      DoSomething();
//  }
}
Using logical operators for tiny if-statements

You can use logical operators as if-statements, but do it all inside one single statement!

For example, I wrote a fixed version of valstr and I did it like this without thinking too much about it:
pawn Code:
stock FIXES_valstr(dest[], value, bool:pack = false)
{
    static const cellmin_value[] = !"-2147483648";

    if (value == cellmin)
        pack && strpack(dest, cellmin_value, 12) || strunpack(dest, cellmin_value, 12);
    else
        format(dest, 12, "%d", value), pack && strpack(dest, dest, 12);
    // Notice the comma after format? See the section "Multiple actions in one statement" in this topic.
}

As you can see, there are logical operators (&& and ||) just out in the open.

The code above, without this little trick, would look something like this:
pawn Code:
stock FIXES_valstr(dest[], value, bool:pack = false)
{
    static const cellmin_value[] = !"-2147483648";

    if (value == cellmin) {
        if (pack)
            strpack(dest, cellmin_value, 12)
        else
            strunpack(dest, cellmin_value, 12);
    } else {
        format(dest, 12, "%d", value);
       
        if (pack)
            strpack(dest, dest, 12);
    }
}

A few examples:
pawn Code:
a && b();              // if a, run b.
a && b() || c();       // if a, run b. otherwise, run c.
a || b();              // if not a, run b.
a && b() || c && d();  // if a, run b. otherwise, if c, run d.
a && b() && c();       // if a, run b. if b isn't false, run c.
Efficient memory management with stock const

Written by ******.

"stock const".

This is something I discovered a while ago, but forgot to ever write up:

pawn Code:
#include <a_samp>

main()
{
    print("hi");
    print("hi");
}

If we compile this with "-a" we get:

Code:
CODE 0	; 0
;program exit point
	halt 0

	proc	; main
	; line 4
	; line 5
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	; line 6
	push.c c
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	zero.pri
	retn


DATA 0	; 0
dump 68 69 0 68 69 0 

STKSIZE 1000
I have highlighted two lines in bold. The first pushes the number "0", the second pushes the number "12" ("c"). "print", for it's first (and only) parameter, takes the address of a string to print. All string literals are converted to data and stored in global memory (I won't go in to the details of the true implications of this, but sufficed to say you change them). "dump" is the current global memory for this tiny program:

Code:
dump 68 69 0 68 69 0
If we convert those numbers to ascii we suddenly see:

Code:
dump 'h' 'i' '\0' 'h' 'i' '\0'
I.e. our two "hi" strings - two copies of the same string isn't very efficient though, so let's improve this by explicitly managing the memory:

pawn Code:
#include <a_samp>

stock const
    C_HI[3] = "hi";

main()
{
    print(C_HI);
    print(C_HI);
}

Code:
CODE 0	; 0
;program exit point
	halt 0


DATA 0	; 0
dump 68 69 0 

	proc	; main
	; line 7
	; line 8
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	; line 9
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	zero.pri
	retn


STKSIZE 1000
The location of "dump" has changed, but that's not important - a large program will have many "dump" statements spread throughout its code, all combined in the final stages of compilation. The important things to look at are the contents of "dump" and the two highlighted lines.

Despite the fact that we are using a variable, this is still EXACTLY as efficient code-wise as the original version because we are still using constant strings. However, the memory requirements for storing the two strings has halved, with both instances pointing to the same memory.

You can also use "new const" or "static stock const", but "stock const" is probably best for this code to avoid warnings and dupilcation of the memory we are trying not to duplicate.

Your assembly output will probably look different if you are not using "-d0".

Last edited by Slice; 29/06/2012 at 05:03 PM.
Slice is offline   Reply With Quote