[Tutorial] Advanced Iterators (foreach/y_iterate).
#5

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Iterators In Iterators

The current implementation of the "Admin" special iterator is not great, but can
be improved slightly by rearranging the loop:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    do
    {
        // Move on to the next player.
        ++cur;
        // Test if they are an admin (includes a connection check).
        if (IsPlayerAdmin(cur)) return cur;
    }
    while (cur != MAX_PLAYERS);
    // Generic "foreach" failure is "-1".
    return -1;
}
This code is still using a normal player loop, which is the exactly the sort of
loop "foreach" was originally designed to replace. Doing so seems to defeat the
entire purpose of these loops, so lets use the real foreach instead via the
additional functions introduced earlier:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    // Get the pre-start value (translate "-1" in to "SIZE_MINUS_1").
    if (cur == -1) cur = Iter_Begin(Player);
    do
    {
        // Get the next player.
        cur = Iter_Next(Player, cur);
        // Run out of players, return failure.
        if (cur == Iter_End(Player)) return -1;
        // Test if the found player is an admin.
    }
    while (!IsPlayerAdmin(cur));
    // Return them.
    return cur;
}
You could also replicate the style used in the standard "foreach" loop with:

pawn Code:
if ((cur = Iter_Next(Player, cur)) == Iter_End(Player)) return -1;
Multi-Dimensional Special Iterators

The MD iterator example from the start of this tutorial was:

pawn Code:
new
    Iterator:OwnedVehicle[MAX_PLAYERS]<MAX_VEHICLES>;
Iter_Init(OwnedVehicle);
// Add vehicles to players here...
Iter_Add(OwnedVehicle[playerid], 42);
Iter_Add(OwnedVehicle[playerid], 43);
Iter_Add(OwnedVehicle[playerid], 44);
// Other code...
foreach (new vehicleid : OwnedVehicle[playerid])
{
    printf("Player %d owns vehicle %d", playerid, vehicleid).
}
Like the "Admin" example this code works fine, but uses a lot of memory. For
500 players and 2000 vehicles the main array takes up "500 * (2000 + 1)" cells,
which is just over 3Mb. That's not a vast amount of memory on modern computers,
but it might still be worth reducing. For some examples a reduction may not be
possible, but in this case a vehicle can only have one owner so there's no point
storing every vehicle for every player. A better storage option would be:

pawn Code:
new
    gVehicleOwner[MAX_VEHICLES];
Here "gVehicleOwner" stores the player ID of the owning player for each vehicle.
To print all the vehicles belonging to one player now looks like:

pawn Code:
for (new vehicleid = 0; vehicleid != MAX_VEHICLES; ++vehicleid)
{
    if (gVehicleOwner[vehicleid] == playerid)
    {
        printf("Player %d owns vehicle %d", playerid, vehicleid).
    }
}
This is a significant reduction in memory, but we had to re-write the code to
accommodate it. Given that we know we can write special iterators that are
functions, can we write a function to hide this change in internal
representation? Yes - quite easily in fact. The original loop was:

pawn Code:
foreach (new vehicleid : OwnedVehicle[playerid])
{
    printf("Player %d owns vehicle %d", playerid, vehicleid).
}
If "OwnedVehicle" is going to become a special iterator function, we need to
pass it the "playerid" we are interested in as an additional parameter. This
parameter comes before the "cur" parameter that holds the vehicle currently
being looked at. From here, using previous information, the code is quite
simple:

pawn Code:
#define OwnedVehicle@YSII_Ag%0- @iterfunc OwnedVehicle()
stock OwnedVehicle@YSII_Ag(playerid, cur)
{
    do
    {
        // The initial value is "-1", increment it to 0, and always increment
        // after that.
        if (++cur == MAX_VEHICLES) return -1;
        // Stay in this function until we find a vehicle this player owns, or
        // we run out of vehicles to test.
    }
    while (gVehicleOwner[cur] != playerid);
    return cur;
}
An additional benefit is that you can't modify special iterators directly. If
we use the first version of "OwnedVehicle" user code can include:

pawn Code:
Iter_Add(OwnedVehicle[playerid], 42);
If you use the special iterator version and try call that function it will
generate a compile-time error. This makes it essentially a read-only iterator
unless you have access to the underlying data store. If this iterator comes
from a vehicle ownership library you may have a function such as:

pawn Code:
Vehicle_SetOwner(vehicleid, playerid);
Which will add that owner to the "gVehicleOwner" array, while checking that the
vehicle isn't already owned and doing anything else. This way you can keep
"gVehicleOwner" as "static" to your library so that no-one can access private
data except through your well-defined API.

More Parameters

We have seen a function with one parameter, and a function with two parameters,
but you can go as far as you like:

pawn Code:
#define MDIter@YSII_Ag%0- @iterfunc MDIter()
stock MDIter@YSII_Ag(par1, par2, par3, cur)
{
    return -1;
}

foreach (new i : MDIter[par1][par2][par3])
{
}
Function-Like Iterators

We have already seen how special iterators are functions but look like normal
iterators. All the examples so far have assumed that the iterator is some data
internal to a library that you want players to be able to loop over. For
example, the "OwnedVehicle" iterator would be defined in a vehicle ownership
library where you would not want the user to have direct access to the internal
information ("gVehicleOwner"). In these cases the library user should not know
HOW the iterator is implemented (be it using special functions or standard
iterators), but sometimes you DON'T want them to look the same.

For example, consider the "y_bit" library. This provides functions for creating
bit arrays - if a user creates a bit array then THEIR code owns that data, not
the "y_bit" library. They know they have a bit array - the fact that they have
one is not an implementation detail of the "y_bit" library. How it WORKS is,
but the fact that it exists isn't.

When you want to access someone else's data you use their variables or arrays,
but when you want to manipulate your own data you pass it to functions. This
idea carries over in to iterators. Looping over other people's iterators looks
like regular variable or array access:

pawn Code:
foreach (new playerid : Player)
{
}
But manipulating your own data that you know is NOT an iterator should look like
a function call:

pawn Code:
new
    BitArray:arr<100>;
foreach (new c : Bits(arr))
{
}
This is a tricky distinction to explain, but hopefully you get the difference.
It doesn't help that all the previous examples didn't distinguish between what
is user code and what is library code. In general, the previous special
iterator function definitions would have been in a library, and the "foreach"
loops using those iterators would have been in user code using that library
(though you can of course use the iterator within the library). This was why I
made the point about not needing to include "y_iterate" in to your library at
all when only using special iterator functions.

"y_bit" provides the "Bits" iterator, which takes a bit array and loops over all
the bits set within it:

pawn Code:
new
    BitArray:arr<100>;
Bit_Set(arr, 42, true);
Bit_Set(arr, 82, true);
Bit_Set(arr, 11, true);
Bit_Set(arr, 99, true);
Bit_Set(arr, 7, true);
foreach (new c : Bits(arr))
{
    printf("%d", c);
}
Output:

pawn Code:
7
11
42
82
99
This iterator takes and manipulates YOUR data, so does NOT look like a variable
or array, but instead looks like a normal function. However, the definition in
code is actually exactly the same as normal:

pawn Code:
#define Bits@YSII_Ag%0- @iterfunc Bits()
stock Bits@YSII_Ag(BitArray:data[], cur)
This takes one parameter that is the bit array to loop over, and a second
parameter that is the standard current value. Actually, that's a slight lie, it
really looks like:

pawn Code:
#define Bits@YSII_Ag%0- @iterfunc Bits()
stock Bits@YSII_Ag(BitArray:data<>, start, size = sizeof (data))
Here we have an additional parameter AFTER the "cur" parameter (now called
"start", just because it can be called anything), but because that parameter
takes a default value it doesn't need to be specified in the "foreach" loop and
it MUST come last. It also uses the bit array "<>" syntax instead of the
normal array "[]" syntax, but this is well documented in the "y_bit" library.
Reply


Messages In This Thread
Advanced Iterators (foreach/y_iterate). - by Ada32 - 14.04.2015, 19:43
Advanced Iterators (foreach/y_iterate) - Part 2 - by Ada32 - 14.04.2015, 19:45
Advanced Iterators (foreach/y_iterate) - Part 3 - by Ada32 - 14.04.2015, 19:47
Re: Advanced Iterators (foreach/y_iterate). - by Pottus - 14.04.2015, 19:49
Advanced Iterators (foreach/y_iterate) - Part 4 - by Ada32 - 14.04.2015, 19:50
Advanced Iterators (foreach/y_iterate) - Part 5 - by Ada32 - 14.04.2015, 19:52
Re: Advanced Iterators (foreach/y_iterate). - by Crayder - 02.05.2015, 07:16
Re: Advanced Iterators (foreach/y_iterate). - by Konstantinos - 02.05.2015, 10:50
Re: Advanced Iterators (foreach/y_iterate). - by Gammix - 10.04.2018, 00:08

Forum Jump:


Users browsing this thread: 1 Guest(s)