Recommended pre-reading material if you do not understand how numbers are stored in memory: https://projectgodmode.com/custom_conte ... number.pdf
The unpacked string
An unpacked string is the type of string that you commonly (probably always) see used in AMX-X plugins. It is an array of 4-byte cells, each cell representing an individual character in the string. Each character in a string (both packed and unpacked) is represented by an integer value of 0-255. To represent an integer of 0-255, only 1 byte is needed, however, unpacked strings allocate 4-bytes per character leaving 3-bytes of unused memory per cell. Below illustrates this:
Code: Select all
A byte consists of 8-bits, below is a cell of 4-bytes or 32-bits.
This is how a character is stored in an unpacked string cell.
Only bits 1-8 are used because that is all that is needed to store values 0-255.
0000 0000 0000 0000 0000 0000 0000 0000 = 0
0000 0000 0000 0000 0000 0000 1111 1111 = 255
The bits in red are un-used\wasted memory. The waste is 3-bytes per cell (24-bits)A packed string is also an array of 4-byte cells but the memory layout is 'packed' so that all memory in the cell is used to store data. This results in the memory requirement for each character to be 1-byte (8-bits) opposed to 4-bytes (32-bits) in an unpacked string. When you declare a packed string you are still declaring an array of 4-byte cells but each cell will hold 4 characters instead of just one as it would in an unpacked string. Note: A packed string can only hold characters from a single-byte character set, such as ascii or one of the extended ascii sets
Code: Select all
This is how a 4-byte cell will appear in packed form. Each color represents
the storage of an individual character in the string. Notice, nothing is wasted.
This is the packed version of the word "pack"
0111 0000 0110 0001 0110 0011 0110 1011Packed
Storing any numerical values in the range of 0 - 255
Storing ascii text
Unpacked
Storing numerical values > 255
Storing unicode text or any non-ascii standard
How do I define and manipulate a packed string in AMX-X?
The usage is almost identical to a normal unpacked string with the exception of accessing the string. You cannot print\manipulate the entire packed string as you can an unpacked. You must first unpack it and then print/format/etc.
Here are two small functions to pack and unpack a string. I don't know how 'expensive' using this unpack function on large strings will be but it's the only option for now. If anyone discovers a way to do this without an additional function please PM me.
Code: Select all
public strpack( const szUnpacked[] , szPacked[] )
{
new i;
while((szPacked{i} = szUnpacked[i++])){}
}
public strunpack( const szPacked[] , szUnpacked[] )
{
new i;
while((szUnpacked[i] = szPacked{i++})){}
} Code: Select all
new szString[11 char] = !"The string"; Code: Select all
Unpacked = szString[5];
Packed = szString{5}; Your memory savings will vary depending on the number and size of the strings in your plugin. I put together a plugin that can quickly determine memory usage based on the number and size of strings in your plugin. By default it is set to use packed strings, to get the result of using unpacked strings, comment the #defined PACKED_STRINGS line.
Here are some results that I came up with using a various number of strings, all with a size of 512. All results are the 'total requirements' value when compiled. I also did a test using size 33 strings and savings can start being noticed at 4+ strings.
Code: Select all
NUM_STRINGS = 1
PACKED = 19756
UNPACKED = 19088
Net savings = -668 bytes [Uses more memory when packed]
NUM_STRINGS = 5
PACKED = 21820
UNPACKED = 27296
Net savings = 5476 bytes
NUM_STRINGS = 10
PACKED = 24400
UNPACKED = 37556
Net savings = 13156 bytes
NUM_STRINGS = 20
PACKED = 29560
UNPACKED = 58076
Net savings = 28516 bytes
NUM_STRINGS = 30
PACKED = 34720
UNPACKED = 78596
Net savings = 43876 bytes
NUM_STRINGS = 50
PACKED = 45040
UNPACKED = 119636
Net savings = 74596 bytes
NUM_STRINGS = 100
PACKED = 70840
UNPACKED = 222236
Net savings = 151396 bytesCode: Select all
#include <amxmodx>
#include <amxmisc>
#define PLUGIN "Packed vs Unpacked"
#define VERSION "1.0"
#define AUTHOR "bugsy"
#define PACK_STRINGS
#define NUM_STRINGS 20
#define MAX_STRING_SIZE 512
#if defined PACK_STRINGS
new g_szUnpackBuffer[MAX_STRING_SIZE];
new g_szStrings[NUM_STRINGS][MAX_STRING_SIZE char]
#else
new g_szStrings[NUM_STRINGS][MAX_STRING_SIZE]
#endif
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
}
public plugin_cfg()
{
for ( new iTest = 0 ; iTest < NUM_STRINGS ; iTest++ )
{
#if defined PACK_STRINGS
g_szStrings[iTest] = !"Packed strings"
strunpack( g_szStrings[iTest] , g_szUnpackBuffer );
server_print( "%d = %s" , iTest , g_szUnpackBuffer );
#else
g_szStrings[iTest] = "Unpacked strings";
server_print( "%d = %s" , iTest , g_szStrings[iTest] );
#endif
}
}
#if defined PACK_STRINGS
public strunpack( szPacked[] , szUnpacked[] )
{
new i;
while((szUnpacked[i] = szPacked{i++})){}
}
#endif Since a packed string still uses 4-byte cells, you will usually get from 1 to 3 bytes of wasted space. This is because you cannot have only a partial cell. Suppose you want to store 5 letters in a packed string. Theoretically you only need 5 bytes right? since that's what this tutorial has been explaining. Yes, you only need 5 bytes for the storage of your string but you will need 2 cells since 1 cell can only hold 4 characters. In this case we would have 4 bytes used in the first cell for the first 4 letters and then only use 1 byte in the second cell for the remaining letter, plus 1 extra cell for the null character. The left over 2 cells are referred to as 'padding' and will remain there empty holding 0's. The only time this will not occur is when your string size is divisible by 4.
Here are some sample AMX-X scripts using packed strings
Code: Select all
//This will declare a packed and unpacked string, display the values in
//each cell (which are identical) and then display the size of each string
//(in cells) at the end.
//The result: Same data 0 = 0 -> Same data 31 = 31
//Packed Size = 9 cells
//Unpacked Size = 33 cells
//Why is the packed 9 cells? See Padding section above.
new szPacked[33 char];
new szUnpacked[33];
for ( new i = 0 ; i < 33 ; i++ )
{
szPacked{i} = i;
szUnpacked[i] = i;
server_print( "Same data %d = %d" , szPacked{i} , szUnpacked[i] );
}
server_print( "Packed Size = %d cells" , sizeof szPacked );
server_print( "Unpacked Size = %d cells" , sizeof szUnpacked ); Code: Select all
//This will print out the ascii values for each character
//in the string "Packed"
new szPacked[7 char] = !"Packed";
for ( new i = 0 ; i < 7 ; i++ )
server_print( "%d" , szPacked{i} );
//This will print the ascii values for "Packed": 80, 97, 99, 107, 101, 100, 0
//P=80 a=97 c=99 k=107 e=101 d=100 NULL=0Code: Select all
//Define a packed array used to store bool values for players
new g_szOnline[33 char];
public client_putinserver(id)
{
g_szOnline{id} = 1;
}
public client_disconnect(id)
{
g_szOnline{id} = 0;
}
public YourFunc(id)
{
if ( !g_szOnline{id} )
//user not online
} 