GML What's the Difference: Loop Structures vs. Step Checks and Alarms

FrostyCat

Member
What's the Difference: Loop Structures vs. Step Checks and Alarms

GM Version: GMS 2.x, GMS 1.x
Target Platform: All
Download: N/A
Links: N/A
Summary: An explanation of when to use loops and when not to, plus a brief overview of non-blocking repetition


Summary
Many recent novices needlessly crash their projects by misusing while, do-until and other blocking loops. This guide aims to document situations where a loop should or should not be used, plus the basics of asynchronous repetition for when loops are inappropriate.


Blocking (synchronous) vs. Non-Blocking (asynchronous)

An operation is blocking or synchronous if it halts the runtime environment until it is done. Examples include the basic looping statements (for, while, do-until, repeat, with) and the blocking message functions (e.g. get_string(), show_message()).

An operation that runs in the background without halting the runtime environment is non-blocking or asynchronous. Examples include the step/draw event cycle, HTTP requests and non-blocking message functions (e.g. get_string_async(), show_message_async())

When to Use Loops

Loop structures such as for, while, do-until and repeat are blocking operations and must always be used with care, particularly while and do-until. Loop structures should only be used:

- When it is OK to repeat the action(s) all at once without waiting for the next step
- When the state of execution is invalid or unwanted until the looping condition is satisfied
- When action(s) taken within the loop work toward satisfying the looping condition (this includes the increment in a for loop)

Examples of when to use loops:

- Finding a random empty spot
Code:
//OK!
var xx, yy;
do {
  xx = random(room_width);
  yy = random(room_height);
} until (position_empty(xx, yy));
- Reading all lines from a file
Code:
//OK!
var str = "",
    f = file_text_open_read("file.txt");
while (!file_text_eof(f)) {
  str += file_text_read_string(f) + chr(10);
  file_text_readln(f);
}
file_text_close(f);
- Generating a cluster of effects
Code:
//OK!
for (var dir = 0; dir < 360; dir += 45) {
  effect_create_above(ef_firework, x+lengthdir_x(128, dir), y+lengthdir_y(128, dir), 1, choose(c_red, c_yellow, c_lime));
}
- Drawing copies of an image, shape or piece of text
Code:
//OK!
var xx = x;
repeat (lives) {
  draw_sprite(spr_heart, 0, xx, y);
  xx += sprite_get_width(spr_heart);
}

When NOT to Use Loops


NEVER check a condition in loop form if you answer "yes" to any of the following:

- Do the actions within need to repeat gradually over time in order to be meaningful?
- Do you need other background processes to continue running while it is repeating? (e.g. speed-based movement, alarms/countdowns, user input, networking packets)
- Do the actions that work toward the stopping condition lie outside the block?
- Does the repeating condition rely on a background process to work properly?
- Does the repeating condition involve real-time user input? (e.g. keyboard, mouse, gamepad and touch screen presses)

Examples of when NOT to use loops:

- Fading effects
Code:
//NO!
while (image_alpha > 0) {
  image_alpha -= 0.1;
}
Moving towards a target
Code:
//NO!
x = 0;
repeat (room_width div 4) {
  x += 4;
}
Waiting for user keystrokes
Code:
//NO!
while (keyboard_check(vk_up)) {
  y -= 2;
}
Checking a winning condition
Code:
//NO!
do {
  effect_create_above(ef_rain, 0, 0, 1, c_white);
} until (!instance_exists(obj_enemy))
Exception: It is OK to use a loop to help compute a condition, as long as it is finite and self-contained. For example, it is OK to use a loop to help search the rows on a tic-tac-toe board for a win as part of a step check (more on that later), but NOT OK to use a loop to keep music playing while nobody has won.


Asynchronous Repetition

Now that you know what kinds of repetition should and shouldn't be handled with loops, you should learn what to do with those that shouldn't. The Step and Alarm events are your primary tools for handling these.

Step Checks

The step check is the most basic form of asynchronous repetition. It takes the form of an if statement in the Step event (sometimes Begin Step or End Step), which repeats once upon every frame while the game runs. Conditions appropriate for step checks include most user input, winning conditions and collision checks, among others.

Example: "While the right arrow key is held down, move 5 pixels to the right"

Step:
Code:
if (keyboard_check(vk_right)) {
  x += 5;
}
Controlling Step Checks

Sometimes you need to control the repetition of step checks, for example playing a sound when the player is hit but not while it stays hit. This requires you to implement a flag variable, which is covered in this article: Restricting Unwanted Repetition with Flags

Collision, Keyboard and Mouse Events

GM comes with a rich variety of built-in events that operate like step checks. Consult your version's Manual for a full listing and what condition each event corresponds to. You can use these as needed to visually delineate code and inherit event behaviours separately.

Caution: In a collision event, other represents the colliding instance. The same is NOT TRUE in a step check. If you need to access the colliding instance from a step check, you must use a collision-checking function that returns an instance ID, then access the colliding instance through that.

Collision event with foo:
Code:
with (other) {
  instance_destroy();
}
Equivalent step check:
Code:
var inst = instance_place(x, y, foo);
if (inst != noone) {
  with (inst) {
    instance_destroy();
  }
}
Delayed Repetition Using Alarms

To space apart recurrences beyond once per step, alarms offer a simple solution. Each object comes with up to 12 alarms, numbered 0 through 11. Once an alarm's event is filled in and the alarm is set to a value above 0, it will decrement by 1 every step until it reaches 0, at which point the alarm's event is executed once.

To repeat one or more actions every q steps using alarm n:

Alarm n event:
Code:
/* Action(s) */
alarm[n] = q;
To start the cycle: (you can also set to 1 to cut to the first recurrence faster)
Code:
alarm[n] = q;
To stop the cycle: (note that this will not run the upcoming recurrence)
Code:
alarm[n] = -1;
Caution: Be careful about setting alarms in the Step event or any other recurrent event. If you want to avoid setting the alarm again while it is counting down, you should check that the alarm is unset (i.e. alarm[n] < 0) before setting the alarm.

Variation: Delayed Repetition Using Step Checks

See "rate-capping flags" in: Restricting Unwanted Repetition with Flags

The cycle can be started by setting the flag to true, and stopped by setting the flag to false.
 

Morne

Member
Excellent advice! Especially:
Caution: In a collision event, other represents the colliding instance. The same is NOT TRUE in a step check. If you need to access the colliding instance from a step check, you must use a collision-checking function that returns an instance ID, then access the colliding instance through that.
Should be a sticky
 
Thank you for this information (and also for taking the time to format it so nicely.) I find programming tutorials hard to read, and even harder to digest. On the contrary: I found this one to be very clear and concise. I've bookmarked this topic for quick reference.
... I love you
 

Selek

Member
Just found this. Extremely helpful! It helped me solved a problem simply and quickly. Thank you for posting it.
 
Top