y_iterate aka foreach - what is it, and how to use it

As I mentioned previously, we'll start our journey with YSI at y_iterate. Before I even joined sa-mp forums, I knew of foreach. It's trivial in use, but only handful of people know how it works, and even less use other iterators than Players. Y_Less had already written quite detailed description of this include, refer to it for API.

Why foreach is faster than standard player loop?

Normally you would have to loop MAX_PLAYERS times, then use IsPlayerConnected to determine whether player is on server. Then come additional checks, like whether player is registered, logged in, etc. YSI has a solution for that, y_groups, which I will cover in upcoming tutorial. Anyway, what y_iterate does, is simply to hook to OnPlayerConnect callback, then add player to Players iterator, and then remove player from it on OnPlayerDisconnect (you can adjust settings for bots using defines before including y_iterate).

But arrays in PAWN are static, how on earth would you know when to stop looping?

Remember that Players is an iterator. You can write one yourself, so let's do just that:

#include <YSI\y_iterate>
//(...)
new
    Iterator:Foobar<10>
;

Pawn compiler has three stages: preprocessing - substituting all #define in code, then first compiler pass, then second one. Y_iterate magic happens during preprocessor pass. If you want to see what happens then, download modified PAWN.sublime-build. Now press ctrl+shift+p, type build list, and select Build: List file. In your gamemodes there should appear new file <project name>.lst. Open it. Scroll all the way down. Now you should see something like

new
    _Y_ITER_C9:Foobar@YSII_Cg,Foobar@YSII_Ag[(10)+1]={(10)*2,(10)*2-1,...}
;

What the hell does it mean? Let's make it a little bit more human-readable:

new
    _Y_ITER_C9:Foobar@YSII_Cg,
    Foobar@YSII_Ag[(10)+1]={(10)*2,(10)*2-1,...}
;

_Y_ITER_C9 Is a tag, don't worry about that. Foobar@YSII_Cg is a variable keeping count of how much items we have in our Foobar@YSII_Ag iterator. That's it, we don't need to know more.

Cool, we already knew that from original tutorial. What now?

I think the reason why people don't use y_iterate, is because they don't know where to use it. We'll do a real life example - loading data from database. Let's say houses. I'll use blueg mysql plugin, but don't panic, I'll create a tutorial for that in time. In my database I have 3 houses, however this is just an example, so all they contain is the house name. Github repository for this example.

#include <a_samp>
#include <a_mysql>
#include <YSI\y_iterate>

forward LoadHousesCallback();

#define MAX_HOUSES 10
#define MAX_HOUSE_NAME (32 + 1)

enum E_HOUSE
{
    hdbID,
    hName[MAX_HOUSE_NAME]
}

new
    handle,
    Iterator:Houses<MAX_HOUSES>,
    Houses[MAX_HOUSES][E_HOUSE]
;

public OnGameModeInit()
{
    handle = mysql_connect("localhost", "root", "test", "");
    //Do some handle checking first!

    LoadHouses();
}

LoadHouses()
{
    mysql_tquery(handle, "SELECT * FROM houses", "LoadHousesCallback");
}

public LoadHousesCallback()
{
    new
        rows = cache_get_row_count(handle)
    ;

    if (!rows) return print("It seems there are no house rows, sorry");

    for (new row = 0; row != rows; ++row) 
    {
        if (row == MAX_HOUSES) {
            printf("Number of houses in your database (%d) is larger than " #MAX_HOUSES " slots can handle", rows);

            //We can't load remaining rows :(
            break;
        }

        Houses[row][hdbID] = cache_get_field_content_int(row, "id", handle);
        cache_get_field_content(row, "name", Houses[row][hName], handle, MAX_HOUSE_NAME);
        Iter_Add(Houses, row);
    }

    foreach(new house : Houses) {
        printf("%d. DbID %d, name %s", house + 1, Houses[house][hdbID], Houses[house][hName]);
    }

    return 1;
}

public OnGameModeExit()
{
    mysql_close(handle);

    return 1;
}

main() {
}

That's quite a chunk of code, but we'll go through it step by step.

    mysql_tquery(handle, "SELECT * FROM houses", "LoadHousesCallback");

We request all houses from our database, and when they're fetched, please call LoadHousesCallback.

 if (row == MAX_HOUSES) {
     printf("Number of houses in your database (%d) is larger than " #MAX_HOUSES " slots can handle", rows);

     //We can't load remaining rows :(
     break;
 }

It seems there are too many rows to load from database, and we have only MAX_HOUSES slots. That's unfortunate (but, in the future we'll use y_malloc to handle this).

 Houses[row][hdbID] = cache_get_field_content_int(row, "id", handle);
 cache_get_field_content(row, "name", Houses[row][hName], handle, MAX_HOUSE_NAME);
 >>> Iter_Add(Houses, row);

This is where magic happens, we add our newly loaded house to our iterator. That's it, nothing more is reuqired.

foreach(new house : Houses) {
    printf("%d. DbID %d, name %s", house + 1, Houses[house][hdbID], Houses[house][hName]);
}

As you can see we can use our Houses iterator, and it works just fine. And, even though we specifiec that MAX_HOUSES can handle 100 slots, this foreach will run only 3 times. How cool is that?

One second, you named your iterator exactly like your array. How?

It's simple, go back a few paragraphs. Iterator: is just a macro, so we have a new array, and a variable:

new
    _Y_ITER_C9:Houses@YSII_Cg,
    Houses@YSII_Ag[(100)+1]={(100)*2,(100)*2-1,...}
;

So, in fact we have 3 different names: Houses, Houses@YSII_Cg, and Houses@YSII_Ag. It's really handy for us!.

Conclusion

That's it! I have shown you how to handle y_iterate in real world. I guess there are many question marks on your mind, but become a programmer - try, until something works. Or just ask me - either here in comments, or create a new thread in scripting help. After we'll go through mysql plugin and y_iterate, I'll show you more real-life usages of y_iterate.

Cheers