26.01.2011, 08:48
(
Last edited by Slice; 29/06/2012 at 05:03 PM.
)
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:
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 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.Simple int -> bool conversion
The structure of the operator is this:
condition ? true : false
Here are a couple of examples:
With the ternary operator:pawn Code:if ( a == b )
c = d;
else
c = e;
A couple examples:pawn Code:c = ( a == b ) ? d : e;
// if-^ then-^ ^-elsepawn 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") ) )
);
Sometimes you end up having to convert an int to a bool, the proper way to do that would be:Fastest string loop
This will make myBool become 0 if myInt equals 0, otherwise 1.pawn Code:new myInt = 50;
new bool:myBool = !!myInt;
This is, by my experience, the fastest way to loop through a string.Fastest plain player-loop
pawn Code:for ( new i, l = strlen( string ); i != l; i++ )
{
// ..
}
Note that foreach is faster than this - that's why the heading says plain player-loop.Short functions
pawn Code:for ( new slots = GetMaxPlayers( ), i; i < slots; i++ )
{
if ( !IsPlayerConnected( i ) )
continue;
// code for connected players
}
If your function has only one statement - you can declare them like this:Multiple actions in one statementpawn Code:stock SomeFunction( someInput )
return someArray[ someInput / 2 ];
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:Running code just after a function finishespawn 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;
}
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.Getting rid of stupid tag warnings
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:
Using IsPlayerAdmin inside OnRconLoginAttempt doesn't work - the admin-status is set after that function executes. Example: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!
}
Here's a part of a post by Y_Less explaining how he uses this: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!" );
}
Quote:
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:
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);
TD_SetShadow(td, 3);
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:TD_Colour(td, 0xFF0000AA, false);
TD_SetShadow(td, 3, false);
TD_Font(td, 2);
That way the "TD_Delay" function is always called after the last current update is applied, without knowing a user's code in advance.pawn Code:if (YSI_g_sTimer[td] == -1)
{
YSI_g_sTimer[td] = SetTimerEx("TD_Delay", 0, 0, "i", td);
}
When putting Text3Ds, DBResults, and stuff inside functions such as printf, format, SetTimerEx, CallLocalFunction, CallRemoteFunction you might notice you're getting a tag warning."Char-arrays"
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 );
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.Split up numeric literals
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?
If you use "char arrays" instead of normal ones where needed 50 times you'll save 75,000 bytes (~73 kb).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!
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 } )
{
// ..
}
}
Bit-flags in enumsQuote:
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:
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:Code:345,234,148
And you can split HEX numbers up every 4, or binary numbers every 8:pawn Code:345'234'148
As you can see, the highlighter doesn't like this.pawn Code:0x12FD'39C5
0b00000000'11111111'01010101
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.Using logical operators for tiny if-statements
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();
// }
}
You can use logical operators as if-statements, but do it all inside one single statement!Efficient memory management with stock const
For example, I wrote a fixed version of valstr and I did it like this without thinking too much about it:
As you can see, there are logical operators (&& and ||) just out in the open.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.
}
The code above, without this little trick, would look something like this:
A few examples: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);
}
}
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.
Written by Y_Less.
"stock const".
This is something I discovered a while ago, but forgot to ever write up:
If we compile this with "-a" we get:pawn Code:#include <a_samp>
main()
{
print("hi");
print("hi");
}
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: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
If we convert those numbers to ascii we suddenly see:Code:dump 68 69 0 68 69 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:Code:dump 'h' 'i' '\0' 'h' 'i' '\0'
pawn Code:#include <a_samp>
stock const
C_HI[3] = "hi";
main()
{
print(C_HI);
print(C_HI);
}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.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
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".