Abstracting loops

When you find you must perform the same loop logic in various places in your program, you may use a one-off for_expansion to abstract the pattern.

For example, let’s say we have players, a buffer array of Player structs, and as players join over the network this array gets populated while player_count is incremented. You will want to loop over the connected players in various places in your code, and each time you do you must use the same loop logic, which will be one of these:

// option (a)

for player_index: 0 .. player_count - 1 {
    player := players[player_index];
    // ...
}


// option (b)

for player, player_index: players {
    if player_index >= player_count  break;
    // ...
}

Neither of these options are especially nice; there’s more noise than we’d like, and both take two lines to set up (allowing for the possibility of a mistaken omission, particularly with the second option).

We can use a for_expansion to clean this up:

active_players: struct {};
for_expansion :: (_: *type_of(active_players), body: Code, flags: For_Flags) #expand {
    for `it, `it_index: players {
        if it_index >= player_count  break;
        #insert body;
    }
}

Now, any time we want to loop over the active players we simply do this:

for player: active_players {
    // ...
}
2 Likes

I made some working code to illustrate the above:

#import "Basic";

Player :: struct {
    name: string;
    score: u8;
}

players: [..]Player;

for_expansion :: (_: *type_of(players), body: Code, 
                  flags: For_Flags) #expand {
    for `it, `it_index: players {
        print("inside macro! \n");
        if it_index >= players.count  break;
        #insert body;
    }
}

player_loop :: (_: *type_of(players), body: Code, 
                  flags: For_Flags) #expand {
    for `it, `it_index: players {
        print("inside macro player_loop! \n");
        if it_index >= players.count  break;
        #insert body;
    }
}

main :: () {
    p1 := Player.{"Jane", 82};
    p2 := Player.{"John", 75};
    array_add(*players, p1);
    array_add(*players, p2);
    for players     print("Player no % is %\n", it_index, it);
    /*
    Player no 0 is {"Jane", 82}
    Player no 1 is {"John", 75}
    */

    for player: players {
        print("Player is %\n", player);
    }
    /*
    Player is {"Jane", 82}
    Player is {"John", 75}
    */

    // This uses the for_expansion macro
    for :for_expansion   player: players {
         print("Player is %\n", player);
    }
    /*
    inside macro!
    Player is {"Jane", 82}
    inside macro!
    Player is {"John", 75}
    */

     // This uses the player_loop macro
    for :player_loop   player: players {
         print("Player is %\n", player);
    }
    /*
    inside macro player_loop!
    Player is {"Jane", 82}
    inside macro player_loop!
    Player is {"John", 75}
    */
}
1 Like

for player: active_players is a very nice example for a custom for expansion, however the loop condition player_index < player_count could easily be achieved by using the .count of an array view as the player count, for example:

players: [MAX_PLAYERS] Player;

active_players : [] Player = players;
active_players.count = player_count;

for player, player_index : active_players {
}

I’d therefore suggest to modify the example to implement a loop of the form

for player, player_index: players {
    if !player.is_active  continue;
    // ...
}

to give an example that is not easily achievable by using the built-in array features.

1 Like

Please note that your example does not really capture what the original cookbook entry illustrates because you used players.count instead of a separate variable players_count like the original entry.
Therefore, the condition it_index >= players.count in your code will always be false automatically, and the for expansion does not add any functionality over what a plain for loop over players would do.

Granted, using players_count that is so similar in name and logic to players.count is a weakness of the original cookbook entry, as I have commented there.