[Tutorial] Packed strings in PAWN
#1

Packed strings in PAWN

Introduction
Packed strings have been a feature in SA-MP and PAWN since the beginning. However, many people are unaware of packed strings and the memory reduction! This tutorial will teach you the basics of packed strings, and how to manipulate them accordingly.

What is a packed string?
A packed string is an array that stores data in each byte, rather than each cell, opposed to regular arrays. Packed strings are stored in Little Endian (meaning, lower byte first) and can only hold ASCII characters from 0 to 255, and anything exceeding those numbers will just wrap around.

Picture this code:

pawn Код:
new string[5];

string[0] = 'a';
string[1] = 'b';
string[2] = 'c';
string[3] = 'd';
string[4] = '\0';
'a' is stored in cell 0, 'b' is stored in cell 1, and so on. A cell is basically 4 bytes, so if we do the math, that string above is approximately 20 bytes in size, so each character is stored in a 4 byte set.

However, with this code:

pawn Код:
new string[5 char];

string{0} = 'a';
string{1} = 'b';
string{2} = 'c';
string{3} = 'd';
string{4} = '\0';
'a' is stored in byte 0, 'b' is stored in byte 1, and so on. Basically, that string above is only 8 bytes in size and contains only 2 cells!

You're probably wondering why that string isn't 5 bytes. Using "char" automatically rounds the number to the next nearest multiple of 4 (for example, 1 turns into 4, 3 turns into 4, 5 turns into 8, 23 turns into 24, and so on).

pawn Код:
// This array is 5 cells big and 20 bytes in size.
new string[5] = "abcd";

// This array is 2 cells big and 8 bytes in size.
new string[5 char] = !"abcd"
So you're saving 3-4 times the memory with packed strings!

Areas of use
You might think that packed arrays are useless. If so, then you're wrong. There are many uses for packed arrays, and not just for memory reduction!
  • Sparse arrays

    Firstly, read my tutorial on sparse arrays here:

    https://sampforum.blast.hk/showthread.php?tid=480439

    Sparse arrays are simply arrays that have most of it's data empty very often. However, you can simply save some memory by using packed arrays!

    pawn Код:
    #define MAX_ITEMS (64)

    // 8,192 cells = 32,768 bytes!
    new gData[MAX_ITEMS][128];

    // 2,048 bytes = 8,192 cells!
    new gData[MAX_ITEMS][128 char];

  • Infrequently used strings

    Very often, you'll find yourself saving strings into memory and infrequently using them (e.g. rarely using them). Packed arrays are a great use for these type of arrays.

  • Mass data storage

    Going back to the "sparse arrays" section above, if you have mass data storage then it's best to use packed strings.

  • Memory reduction!

    Packed arrays save you 4 times less the memory, so why store data which is 4 times more memory consuming, when you can use packed arrays?
Unsupported in SA-MP
Packed strings are fluently supported in pure PAWN. However, most of the SA-MP natives do NOT support packed strings, such as format, GetPlayerName, etc.

If you plan on using packed strings, you have to rely on "strpack" and "strunpack" and the other string functions, which you can find in string.inc.
  • Formatting strings

    The format and printf functions don't support packed strings, so you'll have to use strpack:

    pawn Код:
    new
        string[128 char];

    strpack(string, "Hello world!");
    You can also do this:

    pawn Код:
    new
        string[128 char],
        temp[128]
    ;

    strpack(string, "Emmet");
    strunpack(temp, string);

    format(temp, sizeof(temp), "%s likes to eat %s.", temp, "Big Macs");
    strpack(string, temp);
    But that's a very messy way to do it. I'm writing a library that has integrated format support.

    Edit: As of December 14, 2013, you can now use format with packed strings, using my library!

    https://sampforum.blast.hk/showthread.php?tid=481257

  • Functions that ARE supported

    All of the string functions in string.inc support both packed and unpacked arrays.

    The fread and valstr function also accepts an optional "pack" parameter for support with packed arrays, so there's no need to worry whether they will work with them or not.
Accessing the data
Unpacked arrays in PAWN store data in each cell. Packed arrays in PAWN store data in each byte, which means that you can't use square brackets, like you do with unpacked arrays, to access and obtain data inside packed arrays.

pawn Код:
// Incorrect.
if (g_PackedString[0] != '\0')
{
    g_PackedString[0] = 'h';
    g_PackedString[1] = 'i';
}

// Correct!
if (g_PackedString{0} != '\0')
{
    g_PackedString{0} = 'h';
    g_PackedString{1} = 'i';
}
Also, to set a packed string, you have to place an exclamation mark before the string, to denote that it's a packed input.

pawn Код:
// Incorrect. This will try and store the string in each cell, which is not what we want!
g_PackedString = "Hello world.";

// Correct!
g_PackedString = !"Hello world.";
Conclusion
There is more information on packed strings in the PAWN language guide - you can get it here:

https://sampforum.blast.hk/showthread.php?tid=289258

You can also read the "String Manipulation" guide, which covers most information about packed strings:

http://www.compuphase.com/pawn/String_Manipulation.pdf

Anyways, I hope you learned something new today (and if you didn't, go sit in the corner and think about what you've done). Thanks for reading!
Reply
#2

Nice way to save some space, will come back into the future when I will need to save more memory. :P


Edit: Hello future me!
Reply
#3

Quote:
Originally Posted by ******
Посмотреть сообщение
4 * 4 = 12

Err...
Woops, hahahaha, let me change that :D.
Reply
#4

Quote:
Originally Posted by ******
Посмотреть сообщение
I edited my last post, but I should say it is good to see more on these - I know I don't use them as often as I probably should. Slice advocates them a lot though and its good to see others doing the same.
Yeah, thanks for clearing that up! I edited the post with the new information.

I agree, it's nice to see others utilizing packed strings as well, but there are others that don't and should use it where appropriate, like for example they still use the standardized 2 dimension arrays for mass data storage.
Reply
#5

Thanks, just fixed that. I keep forgetting that using "char" with any number that's not a multiple of 4 will simply round up to the nearest multiple of 4! I should probably note that in the first post.
Reply
#6

It does not only benefit for strings, it also benefits plain data aswell. It allows you to have easier bytewise access to a larger memory field.

pawn Код:
new something[1];
//...
something{0} = varchar_1;
//...
something{1} = varchar_2;
//...
something{2} = varchar_3;
//...
something{3} = varchar_4
instead of
pawn Код:
new something;
//...
something = (something&0xFFFFFF00)|varchar_1;
//...
something = (something&0xFFFF00FF)|(varchar_2<<8);
//...
something = (something&0xFF00FFFF)|(varchar_3<<16);
//...
something = (something&0xFFFFFF)|(varchar_4<<24);
Reply
#7

Yeah, thanks for the reminder! Although this is merely a tutorial on strings, I'll add it for the sake of completing the tutorial (after all, it's still in the same category).

I just noticed that rBits and y_bit use cell shifting and packed arrays too. Awesome!
Reply
#8

Oh Emmet _, thank you very much.

I did not like to translate from English the Spanish, and to translate other things since this translator is a garbage, but it was worth a sorrow reading everything.

You gave to me a great push to optimize my GM, thousands of graces my shaggy friend .
Reply
#9

I do use packed strings and I had to reset the string for something I was making. I tried to use EOS ('\0') but it seems that it doesn't make the packed string NULL.

I run this code (here: http://slice-vps.nl:7070/#):
pawn Код:
#include <a_samp>

#if !defined isnull
    #define isnull(%1) \
        ((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1]))))
#endif

static stock
    Player_Name[MAX_PLAYERS][MAX_PLAYER_NAME char];
   
main()
{
    strpack(Player_Name[0], "Konstantinos", MAX_PLAYER_NAME);
    SetTimer("PrintName", 1000, false);
}

forward PrintName();
public PrintName()
{
    new
        sz_T[MAX_PLAYER_NAME];
   
    strunpack(sz_T, Player_Name[0], MAX_PLAYER_NAME);
    printf("Player_Name: \"%s\" -> %s", sz_T, (isnull(sz_T)) ? ("NULL") : ("NOT NULL"));
    Player_Name[0]{0} = EOS;
    SetTimer("PrintName2", 1000, false);
}

forward PrintName2();
public PrintName2()
{
    new
        sz_T[MAX_PLAYER_NAME];
   
    strunpack(sz_T, Player_Name[0], MAX_PLAYER_NAME);
    printf("Player_Name: \"%s\" -> %s", sz_T, (isnull(sz_T)) ? ("NULL") : ("NOT NULL"));
}
The output was:
pawn Код:
Player_Name: "Konstantinos" -> NOT NULL
Player_Name: "sts" -> NOT NULL
I then used:
pawn Код:
#include <a_samp>

#if !defined isnull
    #define isnull(%1) \
        ((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1]))))
#endif

static stock
    Player_Name[MAX_PLAYERS][MAX_PLAYER_NAME char];
   
main()
{
    strpack(Player_Name[0], "Konstantinos", MAX_PLAYER_NAME);
    SetTimer("PrintName", 1000, false);
}

forward PrintName();
public PrintName()
{
    new
        sz_T[MAX_PLAYER_NAME];
   
    strunpack(sz_T, Player_Name[0], MAX_PLAYER_NAME);
    printf("Player_Name: \"%s\" -> %s", sz_T, (isnull(sz_T)) ? ("NULL") : ("NOT NULL"));
    strpack(Player_Name[0], "\0", MAX_PLAYER_NAME);
    SetTimer("PrintName2", 1000, false);
}

forward PrintName2();
public PrintName2()
{
    new
        sz_T[MAX_PLAYER_NAME];
   
    strunpack(sz_T, Player_Name[0], MAX_PLAYER_NAME);
    printf("Player_Name: \"%s\" -> %s", sz_T, (isnull(sz_T)) ? ("NULL") : ("NOT NULL"));
}
And the output was:
pawn Код:
Player_Name: "Konstantinos" -> NOT NULL
Player_Name: "" -> NULL
It is NULL like I wanted to but is it the correct way of resseting a packed string?
Reply
#10

So... I can do something like this?
pawn Код:
new var[MAX_PLAYERS char];
//---
var{playerid} = 0;
//or
if(var{playerid} == 0)
Reply


Forum Jump:


Users browsing this thread: 4 Guest(s)