FrostyCat
Redemption Seeker
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
- Reading all lines from a file
- Generating a cluster of effects
- Drawing copies of an image, shape or piece of text
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
Moving towards a target
Waiting for user keystrokes
Checking a winning condition
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:
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:
Equivalent step check:
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:
To start the cycle: (you can also set to 1 to cut to the first recurrence faster)
To stop the cycle: (note that this will not run the upcoming recurrence)
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.
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));
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);
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));
}
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;
}
Code:
//NO!
x = 0;
repeat (room_width div 4) {
x += 4;
}
Code:
//NO!
while (keyboard_check(vk_up)) {
y -= 2;
}
Code:
//NO!
do {
effect_create_above(ef_rain, 0, 0, 1, c_white);
} until (!instance_exists(obj_enemy))
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;
}
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();
}
Code:
var inst = instance_place(x, y, foo);
if (inst != noone) {
with (inst) {
instance_destroy();
}
}
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;
Code:
alarm[n] = q;
Code:
alarm[n] = -1;
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.