Thursday, October 18, 2012

foreach 14

foreach 14

Update

Finally found and fixed an out-of-bounds error people were having.

Introduction

foreach replaces some* loops (normally the main player loop) with a faster, more efficient loop. Example:

Code:
for (new i = 0; i != MAX_PLAYERS; ++i)
{
 if (IsPlayerConnected(i))
 {
  printf("Player %d is connected", i);
 }
}

Simply becomes:

Code:
foreach (new i : Player)
{
 printf("Player %d is connected", i);
}

Note that this IS NOT a simple definition to insert the normal loop, it's actually a completely different implementation using lists of players, making it faster that the normal system. Furthermore, because of the way it works, it doesn't matter what MAX_PLAYERS is, whether it's correct for your server or not, this loop will always take the same amount of time as it ONLY loops through connected players, not unconnected players.

* The sort of loops this works well with are loops over "sparse" arrays, explained later.

Download

http://pastebin.com/Seseuh2x

Use

All you need to use this system is:

Code:
#include <foreach>

Then you can replace your loops with the code below.

Code:
foreach (new i : Player)
{
 printf("%d", i);
}

That code will declare a new local variable called "i" and loops it through all connected players, printing out their IDs. No use of "IsPlayerConnected" is required as that is all handled internally for that specific iterator*. The scope of "i" is the same as it would be in "for" loops - i.e. you can't use it after the loop.

Code:
new
 i;
foreach (i : Character)
{
 printf("%d", i);
}

That code uses an existing variable called "i" to loop through the iterator called "Character". This is another pre-defined iterator containing all connected players and all bots. The only pre-defined iterator not listed here is "Bot" (a.k.a. "NPC") which is just all NPs.

* Here "Player" is the name of the "iterator", you can declare custom iterators with their own names. The iterator contains all the data, the loop variable "i" in this example is used to extract the data.

Declaration

To declare a single iterator (a thing you can use foreach on) do:

Code:
new
 Iterator:MyIterator<10>,
 Iterator:IterArray[12]<5>;
Iter_Init(IterArray);

That will declare an iterator called "MyIterator" with 10 slots and an array of 12 5-slot iterators called "IterArray" (note that iterator arrays must be initialised, normal ones don't).

There are three pre-defined iterators: "Player", containing all connected players; "Character", containing all connected players and bots; and "Bot", aka "NPC", containing all connected bots. Many people only ever use foreach for these pre-defined iterators - if so you don't need to worry about any declarations or functions listed here.

An iterator can hold values up to the given limit, not that many random values. For example "23" would be an invalid value in any of the iterators listed above, even if it is the only value. "foreach" loops over the present values in an iterator, by default there are none, they must be added.

Functions
  • Iter_Init(name)

    If you declare a multi-dimensional iterator you must call this function before adding anything to it.

    Code:
    new
     Iterator:SomeMore[12]<5>;
    Iter_Init(SomeMore);
  • Iter_Add(name, value)

    Adds a value to the named iterator. If the value is invalid or already present it is simply ignored.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    foreach (new i : Some)
    {
     printf("%d", i);
    }

    Will print:

    Code:
    5
    9
  • Iter_Remove(name, value)

    Removes a value from the named iterator. If the value is invalid or not present it is simply ignored.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    Iter_Remove(Some, 9);
    foreach (new i : Some)
    {
     printf("%d", i);
    }

    Will print:

    Code:
    5
  • Iter_Clear(name)

    Completely resets a named iterator. Removes all the data.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    Iter_Clear(Some);
    foreach (new i : Some)
    {
     printf("%d", i);
    }

    Will print nothing.
  • Iter_Random(name)

    Efficiently returns a random present value from the named iterator. Useful for selecting a random player.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_Random(Some));

    Will print either "5" or "9".
  • Iter_Contains(name, value)

    Checks if the given value is present in the given iterator.
  • Iter_Count(name)

    Efficiently returns the number of items in an iterator.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_Count(Some));

    Will print:

    Code:
    2
  • Iter_Free(name)

    Return the first unused value in the iterator. Note that it does not add the value to the iterator:

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("Free: %d", Iter_Free(Some));
    foreach (new i : Some)
    {
     printf("%d", i);
    }

    Will print:

    Code:
    Free: 0
    5
    9
    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    Iter_Add(Some, Iter_Free(Some));
    foreach (new i : Some)
    {
     printf("%d", i);
    }

    Will print:

    Code:
    0
    5
    9
  • Iter_SafeRemove(name, value, &prev)

    You cannot use "Iter_Remove" inside a "foreach" loop because if you remove the current value the loop can't know where to go next:

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    foreach (new i : Some)
    {
     Iter_Remove(Some, i);
     printf("%d", i);
    }

    Will print:

    Code:
    5
    It will never print "9" because the loop ends early. To correct this, you can use "Iter_SafeRemove" inside loops - the extra parameter takes a variable in which to store a special "continue" value, while leaving your current loop variable correct:

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    foreach (new i : Some)
    {
     new
      next;
     Iter_SafeRemove(Some, i, next);
     printf("%d", i);
     i = next;
    }

    Will print:

    Code:
    5
    9
    Note the use of "i = next;" at the end of the loop - "foreach" only knows about the "i" value, but "next" currently holds the special continue value, so once you are done with "i" set it to the value of "next".

Examples
  • Example 1

Multi-dimensional iterators. This will make an array of iterators.

Code:
#include <foreach>

new Iterator:Vehicle[4]<20>;

public OnGameModeInit()
{
 // This MUST be called first on multi-dimensional iterators.
 Iter_Init(Vehicle);
 // Put in the FIRST list
 Iter_Add(Vehicle[0], AddStaticVehicle(454, -1364.0269, 1470.2139, 0.3568, 165.0191, -1, -1));
 Iter_Add(Vehicle[0], AddStaticVehicle(484, -1394.0040, 1468.3309, 0.1742, 99.7403, -1, -1));
 Iter_Add(Vehicle[0], AddStaticVehicle(484, -1404.9385, 1507.1971, -0.0963, 60.9265, -1, -1));
 Iter_Add(Vehicle[0], AddStaticVehicle(446, -1603.1550, 1391.8168, -0.8820, 310.2346, -1, -1));
 // Put in the SECOND list
 Iter_Add(Vehicle[1], AddStaticVehicle(446, -1698.8333, 1411.8612, -0.4988, 333.6676, -1, -1));
 Iter_Add(Vehicle[1], AddStaticVehicle(446, -1710.4403, 1430.0688, -0.5722, 322.3057, -1, -1));
 Iter_Add(Vehicle[1], AddStaticVehicle(473, -1623.9312, 1438.3147, -0.2109, 280.8999, -1, -1));
 // Put in the THIRD list
 Iter_Add(Vehicle[2], AddStaticVehicle(473, -1609.0120, 1405.0123, -0.1395, 300.4936, -1, -1));
 Iter_Add(Vehicle[2], AddStaticVehicle(487, -1651.4410, 1302.6608, 7.2126, 310.4509, -1, -1));
 // Put in the FOURTH list
 Iter_Add(Vehicle[3], AddStaticVehicle(487, -1736.8011, 1400.1903, 7.3641, 293.3916, -1, -1));
 // Loop through vehicles in the second list
 foreach (new veh : Vehicle[1])
 {
  ChangeVehicleColor(veh, 0, 10);
 }
 // Loop through vehicles in the first list
 foreach (new veh : Vehicle[0])
 {
  ChangeVehicleColor(veh, 12, 20);
 }
}
  • Example 2

Multi-dimensional iterators. This will make an array of iterators and add vehicles to MULTIPLE lists.

Code:
#include <foreach>

new Iterator:Vehicle[4]<20>;

public OnGameModeInit()
{
 // This MUST be called first on multi-dimensional iterators.
 Iter_Init(Vehicle);
 new
  veh;
 veh = AddStaticVehicle(454, -1364.0269, 1470.2139, 0.3568, 165.0191, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 veh = AddStaticVehicle(484, -1394.0040, 1468.3309, 0.1742, 99.7403, -1, -1);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(484, -1404.9385, 1507.1971, -0.0963, 60.9265, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[2], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(446, -1603.1550, 1391.8168, -0.8820, 310.2346, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(446, -1698.8333, 1411.8612, -0.4988, 333.6676, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 veh = AddStaticVehicle(446, -1710.4403, 1430.0688, -0.5722, 322.3057, -1, -1);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(473, -1623.9312, 1438.3147, -0.2109, 280.8999, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[2], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(473, -1609.0120, 1405.0123, -0.1395, 300.4936, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[3], veh);
 veh = AddStaticVehicle(487, -1651.4410, 1302.6608, 7.2126, 310.4509, -1, -1);
 Iter_Add(Vehicle[0], veh);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 veh = AddStaticVehicle(487, -1736.8011, 1400.1903, 7.3641, 293.3916, -1, -1);
 Iter_Add(Vehicle[1], veh);
 Iter_Add(Vehicle[2], veh);
 Iter_Add(Vehicle[3], veh);
 // Loop through vehicles in the second list.
 foreach (veh : Vehicle[1])
 {
  ChangeVehicleColor(veh, 0, 10);
 }
 // Loop through vehicles in the first list.
 foreach (veh : Vehicle[0])
 {
  ChangeVehicleColor(veh, 12, 20);
 }
}
  • Example 3

Loop through all bots.

Code:
foreach (new botid : Bot)
{
 SetPlayerPos(botid, 0.0, 0.0, 10.0); // Pile all NPCs in the centre of the world
}
  • Example 4

Create a new iterator for 20 vehicles and use it.

Code:
#include <foreach>

new Iterator:Vehicle<20>;

public OnGameModeInit()
{
 Iter_Add(Vehicle, AddStaticVehicle(454, -1364.0269, 1470.2139, 0.3568, 165.0191, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(484, -1394.0040, 1468.3309, 0.1742, 99.7403, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(484, -1404.9385, 1507.1971, -0.0963, 60.9265, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(446, -1603.1550, 1391.8168, -0.8820, 310.2346, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(446, -1698.8333, 1411.8612, -0.4988, 333.6676, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(446, -1710.4403, 1430.0688, -0.5722, 322.3057, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(473, -1623.9312, 1438.3147, -0.2109, 280.8999, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(473, -1609.0120, 1405.0123, -0.1395, 300.4936, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(487, -1651.4410, 1302.6608, 7.2126, 310.4509, -1, -1));
 Iter_Add(Vehicle, AddStaticVehicle(487, -1736.8011, 1400.1903, 7.3641, 293.3916, -1, -1));
 foreach (new vid : Vehicle)
 {
  ChangeVehicleColor(vid, 0, 10);
 }
}

Advanced
  • Special Loops

There are several functions to give you more advanced control over the "foreach" loop and access to iterators:
  • Iter_First(name)

    This function returns the first valid value of the iterator array.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_First(Some));

    Will print:

    Code:
    5
  • Iter_Next(name, cur)

    This function returns the next valid value of the iterator array after the current one.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_Next(Some, Iter_First(Some)));

    Will print:

    Code:
    9
  • Iter_End(name)

    This function returns an invalid value AFTER the end of the iterator array, which can be used to compare against to see if you are at the finish of a loop:

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    for (new i = Iter_First(Some); i != Iter_End(Some); i = Iter_Next(Some, i))
    {
     printf("%d", i);
    }

    Will print:

    Code:
    5
    9
    This is very similar to the normal "foreach" keyword, but not quite yet. Hopefully from this you can start to see how you can integrate iterators in to other loops and get more custom access to the values. You should also start to be able to see why "Iter_Remove" doesn't work in loops as "Iter_Next" takes the current value, and if that value is no longer in the loop you have a problem, hence "Iter_SafeRemove":

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    for (new i = Iter_First(Some), prev; i != Iter_End(Some); i = Iter_Next(Some, prev))
    {
     Iter_SafeRemove(Some, i, prev);
     printf("%d", i);
    }

    Will print:

    Code:
    5
    9
  • Iter_Last(name)

    This function returns the last valid value of the iterator array.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_Last(Some));

    Will print:

    Code:
    9
    Note that this works backwards through the iterator, which is a vastly less efficient way to go through iterators. There are ways to make reverse iterators more efficient but they are not currently implemented and so this direction should be used sparingly.
  • Iter_Prev(name, cur)

    This function returns the previous valid value of the iterator before the current one.

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    printf("%d", Iter_Prev(Some, Iter_Last(Some)));

    Will print:

    Code:
    5
    As with "Iter_Last" this should be used sparingly.
  • Iter_Begin(name)

    This function returns an invalid value BEFORE the start of the iterator array, which can be used to compare against to see if you are at the finish of a loop:

    Code:
    new
     Iterator:Some<10>;
    Iter_Add(Some, 5);
    Iter_Add(Some, 9);
    for (new i = Iter_Last(Some); i != Iter_Begin(Some); i = Iter_Prev(Some, i))
    {
     printf("%d", i);
    }

    Will print:

    Code:
    9
    5
  • "foreach" Code

Now we have the additional functions, the internal code of the "foreach" macro is equivalent to:

Code:
for (new i = Iter_Begin(Some); (i = Iter_Next(Some, i)) != Iter_End(Some); )

This is fractionally better than using "Iter_First", but only VERY fractionally.

The fine-grained control functions listed above are very interchangable and some are very efficient. "Iter_End" and "Iter_Begin" are both replaced at compile-time with constants. "Iter_First(Some)" is simply "Iter_Next(Some, Iter_Begin(Some))"; similarly, "Iter_Last(Some)" is simply "Iter_Prev(Some, Iter_End(Some))" (although in code the macros are expanded out more). Because of the compile-time constants this can resolve to nothing more than a simple variable lookup. To demonstrate this property further:

Code:
Iter_Prev(Some, Iter_First(Some));

Becomes:

Code:
Iter_Prev(Some, Iter_Next(Some, Iter_Begin(Some)));

Which is just the one before the one after an invalid one, or:

Code:
Iter_Begin(Some);
  • Compile Options

Define these before you include foreach to get special functionality.
  • FOREACH_NO_BOTS

    If you only want to loop through players, use this to exclude the default NPC iterators.
  • FOREACH_NO_PLAYERS

    This excludes all player and bot looping altogether, so you only get the core functionality - no predefined iterators.
  • Experimental

I've made a new version which will hopefully fix the crash caused by using "Kick" inside a player "foreach" loop, needs testing:

http://pastebin.com/iUFrMNtY

No comments:

Post a Comment