14.04.2015, 19:47
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.
Special Iterators
By now you should have a pretty firm grasp on standard iterators. In terms of
how they are defined, used, and how they work internally.
Output:
This is how the "Player" iterator is defined and runs, as are many others.
However, there is another set of iterators, used indistinguishably from this
first set, but defined in a very different way - these are called "special
iterators".
Example 1
Lets say you want to write an iterator to loop through all positive even
numbers. You could try do this:
Output:
At first glance that seems OK. We loop through every integer and add only the
even ones. But there are two issues Firstly the "Iterator:" macro adds on an
extra cell, so we end up with "EvenInt@YSII_Ag[cellmax + 1]". "cellmax" is the
highest possible integer value, so adding 1 to it is an invalid operation and
won't compile. But assuming that it is possible to do that, we would still end
up with an array consisting of 2147483648 4-byte cells - that's exactly 8Gb of
data in your compiled mode! Again, this won't compile.
Fortunately there is another way in the form of a special iterator function:
As before we can now do:
Output:
The important difference is that this will compile and run, and won't be vast!
Explanation
Unfortunately the syntax for special iterators is a little clumsy so I'll
explain it very quickly:
This line declares a function as a special iterator. The syntax for the line is
always the same, regardless of the number of parameters to your iterator
function:
Notice the hyphen after "%0" - without that your code won't compile. The
spacing is also important here - don't try write "IterName ()" for example.
This line actually starts ths special iterator function. "stock" is simply
because you don't know if your iterator will be used or not and you don't want a
warning (unless you have it in your mode and know you will use it, in which case
don't use "stock"). The next part is the function name, which again is:
"IterName@YSII_Ag". The "@YSII_Ag" suffix is a leftover from the standard
iterators.
Standard iterators compile to something similar to:
For special iterators, there is no size, so there is no start or end point using
this scheme. Instead, "-1" is used:
As a side note, this won't work for regular iterators because "-1" is not a
valid array index, so can't be used as the start value without a significant
loss in efficiency.
Anyway: "-1" is passed to the special iterator function at the start of the loop
to get the first value. Here the first value is the first positive even
integer, i.e. 0, so we return that.
As with "-1" as an input being the start of a special iterator, "-1" as a return
value marks the end of the special iterator. Note that this does mean that no
special iterators can ever have "-1" as a valid return unfortunately... In this
case, we need to return "-1" when we run out of positive even integers. The
last number available in signed 32bit integers is "2147483647" - defined in PAWN
as "cellmax", but this is odd so the last positive integer must be 1 less than
that, i.e. "2147483646", which can be written out in full or calculated as
"cellmax - 1". Therefore, when the input to the special iterator is the last
possible positive even integer there can be no further valid returns and instead
"-1" is returned to mark the end of the loop.
This is very simple. For all other input numbers, return the next even number
after that - defined as "cur + 2".
Example 2
An odd numbers iterator would be almost identical, just with different start and
end values:
"@iterfunc" is a macro defined in "y_iterate", but we don't actually USE it here
- remember that "#define" creates a text-based replacement so that macro will
only be USED when "foreach" is used with "OddInt":
The fact that no macros from "y_iterate" are actually called here means that we
can DEFINE a special iterator without including "y_iterate", and can thus leave
that decision up to the USER of the library. If they never use the special
iterator and don't include "y_iterate" then because we never include it either
no excess code is generated.
Example 3
Lets say you want to write an iterator to loop through all currently connected
RCON admins. Resulting in the equivalent of:
A first attempt might try to use standard iterators and hook
"OnRconLoginAttempt". This is possible, but not as easy as it would first
appear due to "OnRconLoginAttempt" not giving a playerid and the chance that
multiple players may share an IP:
We can now do:
Indeed, this is an acceptable solution (and it turns out this is actually the
BEST solution in terms of speed). However, there is an alternative route that
can be taken which is much simpler to write and avoids hooking callbacks:
The first thing to note is that the "-1" input is never explicitly mentioned.
Instead it is handled generically by "i = cur + 1", which makes "i == 0" when
"cur == -1", and that is the correct initial value for looping over players.
The syntax to use this new special iterator version is identical to the syntax
for the original iterator version. The benefit to this being that the
implementation can be swapped out in your library without users having to
adjust their code at all:
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.
Special Iterators
By now you should have a pretty firm grasp on standard iterators. In terms of
how they are defined, used, and how they work internally.
pawn Code:
// Declare the iterator:
new
Iterator:MyIter<55>;
// Add any value between 0 and 54 to the iterator:
Iter_Add(MyIter, 0);
Iter_Add(MyIter, 42);
Iter_Add(MyIter, 54);
// Use the iterator:
foreach (new i : MyIter)
{
print("%d", i);
}
pawn Code:
0
4
6
However, there is another set of iterators, used indistinguishably from this
first set, but defined in a very different way - these are called "special
iterators".
Example 1
Lets say you want to write an iterator to loop through all positive even
numbers. You could try do this:
pawn Code:
#define IsOdd(%0) ((%0) & 1)
#define IsEven(%0) (!IsOdd(%0))
new
Iterator:EvenInt<cellmax>;
for (new i = 0; i != cellmax; ++i)
{
if (IsEven(i))
{
Iter_Add(EvenInt, i);
}
}
foreach (new j : EvenInt)
{
printf("%d", j);
}
pawn Code:
0
2
4
...
even ones. But there are two issues Firstly the "Iterator:" macro adds on an
extra cell, so we end up with "EvenInt@YSII_Ag[cellmax + 1]". "cellmax" is the
highest possible integer value, so adding 1 to it is an invalid operation and
won't compile. But assuming that it is possible to do that, we would still end
up with an array consisting of 2147483648 4-byte cells - that's exactly 8Gb of
data in your compiled mode! Again, this won't compile.
Fortunately there is another way in the form of a special iterator function:
pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
stock EvenInt@YSII_Ag(cur)
{
switch (cur)
{
case -1: return 0;
case cellmax - 1: return -1;
}
return cur + 2;
}
pawn Code:
foreach (new i : EvenInt)
{
printf("%d", j);
}
pawn Code:
0
2
4
...
Explanation
Unfortunately the syntax for special iterators is a little clumsy so I'll
explain it very quickly:
pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
always the same, regardless of the number of parameters to your iterator
function:
pawn Code:
#define IterName@YSII_Ag%0- @iterfunc IterName()
spacing is also important here - don't try write "IterName ()" for example.
pawn Code:
stock EvenInt@YSII_Ag(cur)
because you don't know if your iterator will be used or not and you don't want a
warning (unless you have it in your mode and know you will use it, in which case
don't use "stock"). The next part is the function name, which again is:
"IterName@YSII_Ag". The "@YSII_Ag" suffix is a leftover from the standard
iterators.
pawn Code:
case -1: return 0;
pawn Code:
for (new i = SIZE_MINUS_1; (i = ITER[i]) != SIZE_MINUS_1; )
this scheme. Instead, "-1" is used:
pawn Code:
for (new i = -1; (i = FUNC(i)) != -1; )
valid array index, so can't be used as the start value without a significant
loss in efficiency.
Anyway: "-1" is passed to the special iterator function at the start of the loop
to get the first value. Here the first value is the first positive even
integer, i.e. 0, so we return that.
pawn Code:
case cellmax - 1: return -1;
value marks the end of the special iterator. Note that this does mean that no
special iterators can ever have "-1" as a valid return unfortunately... In this
case, we need to return "-1" when we run out of positive even integers. The
last number available in signed 32bit integers is "2147483647" - defined in PAWN
as "cellmax", but this is odd so the last positive integer must be 1 less than
that, i.e. "2147483646", which can be written out in full or calculated as
"cellmax - 1". Therefore, when the input to the special iterator is the last
possible positive even integer there can be no further valid returns and instead
"-1" is returned to mark the end of the loop.
pawn Code:
return cur + 2;
after that - defined as "cur + 2".
Example 2
An odd numbers iterator would be almost identical, just with different start and
end values:
pawn Code:
#define OddInt@YSII_Ag%0- @iterfunc OddInt()
stock OddInt@YSII_Ag(cur)
{
switch (cur)
{
case -1: return 1;
case cellmax: return -1;
}
return cur + 2;
}
- remember that "#define" creates a text-based replacement so that macro will
only be USED when "foreach" is used with "OddInt":
pawn Code:
foreach (new c : OddInt)
{
}
can DEFINE a special iterator without including "y_iterate", and can thus leave
that decision up to the USER of the library. If they never use the special
iterator and don't include "y_iterate" then because we never include it either
no excess code is generated.
Example 3
Lets say you want to write an iterator to loop through all currently connected
RCON admins. Resulting in the equivalent of:
pawn Code:
foreach (new playerid : Player) if (IsPlayerAdmin(playerid))
{
}
"OnRconLoginAttempt". This is possible, but not as easy as it would first
appear due to "OnRconLoginAttempt" not giving a playerid and the chance that
multiple players may share an IP:
pawn Code:
new
Iterator:Admin<MAX_PLAYERS>;
hook OnRconLoginAttempt(ip[], password[], success)
{
// We can't just access the logged in player directly.
if (success)
{
// Rebuild the iterator.
foreach (new playerid : Player)
{
if (IsPlayerAdmin(playerid) && !Iter_Contains(Admin, playerid))
{
// Add this admin not already in the array.
Iter_Add(Admin, playerid);
}
}
}
}
hook OnPlayerDisconnect(playerid, reason)
{
if (Iter_Contains(Admin, playerid))
{
Iter_Remove(Admin, playerid);
}
}
pawn Code:
foreach (new admin : Admin)
{
// Loop over all the RCON admins.
}
BEST solution in terms of speed). However, there is an alternative route that
can be taken which is much simpler to write and avoids hooking callbacks:
pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
// Loop over all remaining players AFTER the current player.
for (new i = cur + 1; i < MAX_PLAYERS; i++)
{
// Test if they are an admin (includes an "IsPlayerConnected" check).
if (IsPlayerAdmin(i))
{
return i;
}
}
// Generic "foreach" failure is "-1".
return -1;
}
Instead it is handled generically by "i = cur + 1", which makes "i == 0" when
"cur == -1", and that is the correct initial value for looping over players.
The syntax to use this new special iterator version is identical to the syntax
for the original iterator version. The benefit to this being that the
implementation can be swapped out in your library without users having to
adjust their code at all:
pawn Code:
foreach (new admin : Admin)
{
// Loop over all the RCON admins, using the special iterator.
}