6.9 Timing and Async

Aligned outcomes:

SLO 3

Describe, design, implement, and test structured programs using currently accepted methodology.

Learning Objectives

By the end of this section, you will be able to:

In this section, you will learn to:
  • Explain why the draw loop is synchronous and what that means for slow code.
  • Use frameCount to make something happen every N frames.
  • Schedule work with setTimeout and setInterval.
  • Explain why an asynchronous timer does not pause the game.
  • Choose between counting frames and counting seconds.
  • Stop a repeating timer with clearInterval.

This is the last piece the courtyard game needs: bonus coins that spawn periodically during play, and a brief respawn delay if the swinging hazard from §6.8 knocks the player back to the start.

6.9.1 The Loop Is Synchronous

Since Section 5.1 you have known that moSHion calls your draw function about 60 times a second. Your draw function runs to completion before anything else happens. The engine calls it, waits, and only when your last line has finished does it move the physics, redraw the screen, and schedule the next frame.

Definition 6.9.1: Synchronous

Code that runs to completion before anything else can happen. The draw loop is synchronous: each frame finishes entirely before the next one starts.

This is why a slow draw is fatal rather than merely untidy — about 16 milliseconds per frame is the whole budget, your code included.

The engine drives the loop with the browser's requestAnimationFrame, tied to the display refresh, not to a clock. On a struggling machine, every count you have measured in frames quietly becomes wrong. §6.9.4 is about exactly that gap.

6.9.2 Counting Frames

moSHion keeps a running frame count for you in frameCount — the number of frames since the sketch started. frameCount % N === 0 is true once every N frames, which is the standard way to schedule a repeating game event.

Definition 6.9.2: frameCount

A read-only count of frames elapsed since the sketch started. frameCount % N === 0 is true once every N frames, which is the standard way to schedule a repeating game event.

Try It Now 6.9.1

Add a bonus coin spawner to the courtyard's 'play' state: every 180 frames (3 seconds), spawn one extra coin into the coins group at a random x-position along the level and a fixed y of 300.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Every three seconds, a fresh coin appears somewhere along the 900-pixel level — collectible the moment player.overlaps(coins, ...) finds it, since it was spawned straight into the same coins group the overlap check already watches.

6.9.3 Asynchronous Timers

JavaScript has two timer functions that work on a completely different principle from frame counting: setTimeout(fn, ms) runs fn once, after ms milliseconds; setInterval(fn, ms) runs fn every ms milliseconds, forever. Both take a callback and both measure in milliseconds.

Definition 6.9.3: Asynchronous

Work handed to the browser to run later, while the current code carries straight on. setTimeout and setInterval are asynchronous: they schedule a function and return immediately, without waiting.

Synchronous (draw) Asynchronous (setTimeout)
When it runs now, to completion later, when the delay is up
Does it block? yes — nothing else happens no — the game keeps running
Measured in frames milliseconds
Who calls it the engine, every frame the browser, once the timer fires
Try It Now 6.9.2

Add a respawn delay: if the player falls off the left edge of the level (player.x < 0), hide it (visible = false), wait 1.5 seconds using setTimeout, then bring it back at the starting position.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Walk off the left edge and the player disappears; the camera and level keep running exactly as normal for 1.5 real seconds, then the player reappears back at the start. Nothing froze while you waited — that is what "asynchronous" means in practice.

6.9.4 Frames or Seconds?

Count frames when the thing you are timing belongs to the game world — spawn rates, cooldowns — since you generally want them to slow down with the game if it stutters. Count milliseconds when the thing you are timing belongs to the real world — a countdown a player is watching, a respawn delay.

The trap is using frames for something a human is timing. A 1.5-second respawn written as frameCount % 90 is exactly right on a machine hitting 60 frames a second, and quietly wrong on one that is not.

Try It Now 6.9.3

The courtyard game now has two timers: bonus coins every 3 seconds (§6.9.2) and a 1.5-second respawn (§6.9.3). Which mechanism does each correctly use, and what would go wrong if they were swapped?

Solution

Bonus coins correctly use frameCount % 180 — a game-world spawn rate that should slow down along with the rest of the game if the frame rate dips, keeping the difficulty consistent with the action. The respawn correctly uses setTimeout(..., 1500) — a real-world wait the player is watching and expecting to be an actual second and a half. Swapped, a coin spawner on setTimeout would keep firing even if the game itself stuttered to a crawl, spawning coins faster than the game world is actually progressing; a respawn on frameCount % 90 would take noticeably longer than 1.5 seconds on a struggling machine, and the player would have no idea why.

6.9.5 Stopping a Timer

setInterval repeats forever, which means you have to be able to stop it. Both timer functions return an id, and passing that id to clearInterval or clearTimeout cancels it.

An interval nobody stops is a genuine bug and a quiet one — it keeps running after the thing it belonged to is gone. Whenever you write setInterval, decide in the same breath where it gets cleared.

Try It Now 6.9.4

Add a 3-second "Get Ready!" countdown to the courtyard's title screen, using setInterval to tick once a second, that must be cleared once it reaches zero so it cannot keep running into gameplay.

Editor
runs in a sandboxed frame
▶ Press Run to see the output…
Solution
Editor
runs in a sandboxed frame
▶ Press Run to see the output…

The interval owns the once-a-second counting; draw owns the sixty-times-a-second display, reading whatever remaining currently holds. clearInterval(countdown) at zero is what stops it from ticking remaining into negative numbers forever after the countdown is visually done.

6.9.6 Challenge: Extend It Yourself

No starter code this time — you build it. Wire the "Get Ready!" countdown from §6.9.5 into the full state machine from §6.6-6.7: clicking or pressing space on the title screen should move to a new 'countdown' state (3 seconds, ticking via setInterval) before automatically transitioning to 'play' — the player should not be able to move during the countdown.

Hint (try the Challenge yourself first!)

Start the setInterval the moment state becomes 'countdown' (inside the click/space handler, not inside draw, or you'd restart it 60 times a second). Have the interval's own callback set state = 'play' once remaining hits zero, right where it currently calls clearInterval.

Problem Set 6.9

Problem 1. What does it mean to say the draw loop is synchronous?

Solution

Step 1 — Recall the definition: Synchronous means code that runs to completion before anything else can happen (Definition 6.9.1).

Step 2 — Apply it to the draw loop: moSHion calls your draw function about 60 times a second, and each call runs every one of your lines, top to bottom, with nothing interrupting. Only after your last line has finished does the engine move the physics, redraw the screen, and schedule the next frame — so no two frames ever overlap, and nothing can happen "in the middle" of one.

Answer: Saying the draw loop is synchronous means each frame runs to completion — the engine calls draw, waits for every line to finish, and only then moves the physics and schedules the next frame; nothing else can happen during a frame.

Problem 2. Why is a slow draw function worse than merely inefficient?

Solution

Step 1 — Remember the frame budget: At 60 frames a second, one frame gets about \(1000 \div 60 \approx 16.7\) milliseconds, and your draw code is included in that budget.

Step 2 — See what happens when the budget is blown: If draw takes too long, the engine cannot finish the frame in time, so frames get dropped and the whole game stutters or slows down. Because the loop is synchronous, a slow draw doesn't just waste time — it blocks the physics update and the next frame, making the game unplayable rather than merely untidy.

Answer: A slow draw is fatal, not just inefficient: the entire frame budget is only about 16 ms and your code is part of it, so a slow draw blows the budget, drops frames, and makes the game stutter or crawl.

Problem 3. Roughly how many milliseconds does one frame get at 60 frames a second?

Solution

Step 1 — Set up the division: One second is \(1000\) milliseconds, and at 60 frames per second that second is split evenly among 60 frames.

Step 2 — Do the arithmetic: \(1000 \div 60 \approx 16.7\), so each frame gets roughly 16–17 milliseconds.

Answer: About 16 milliseconds per frame (\(1000 \div 60 \approx 16.7\) ms) — and that budget includes your draw code.

Problem 4. What is frameCount, and why can you not set it?

Solution

Step 1 — Say what it is: frameCount is a read-only count of the frames elapsed since the sketch started (Definition 6.9.2). The engine adds 1 to it every time it runs your draw function, so after 5 seconds at 60 fps it reads about 300.

Step 2 — Explain why you cannot set it: The count belongs to the engine, not to you — moSHion increments it as part of driving the loop, and your code merely reads it. If sketches could assign to it, they could corrupt the engine's record of how much game time has passed, so it is kept read-only.

Answer: frameCount is a read-only counter of frames since the sketch started, incremented by the engine itself once per frame; you cannot set it because it is the engine's internal bookkeeping — you can read it, but not write it.

Problem 5. Write the condition that is true once every two seconds using frameCount.

Solution

Step 1 — Convert seconds to frames: At about 60 frames per second, two seconds is \(60 \times 2 = 120\) frames.

Step 2 — Write the modulo condition: frameCount % 120 === 0 is true whenever the frame count is an exact multiple of 120, which happens once every 120 frames — that is, once every two seconds.

if (frameCount % 120 === 0) {
  // runs once every two seconds
}

Answer: frameCount % 120 === 0 — true once every 120 frames, which is once every two seconds at 60 frames a second.

Problem 6. In the respawn example, why does the level keep scrolling and updating while the 1.5-second timer is running?

Solution

Step 1 — Recall what setTimeout actually does: setTimeout(callback, 1500) hands the callback to the browser and returns immediately — it schedules the work for later; it does not wait for it (Definition 6.9.3).

Step 2 — Trace the 1.5 seconds: Because the call returned instantly, the engine keeps calling draw about 60 times a second for the entire 1.5 seconds, so the camera keeps scrolling, the keys keep working, and the world keeps updating. Only when 1500 real milliseconds have elapsed does the browser fire the callback that resets the player's position and makes it visible again.

Answer: The level keeps updating because setTimeout is asynchronous — it schedules the respawn and returns at once, so the draw loop never pauses; the browser simply fires the callback 1.5 seconds later, after the game has been running normally the whole time.

Problem 7. Give one sentence each defining synchronous and asynchronous.

Solution

Step 1 — Define synchronous (one sentence): Take it straight from Definition 6.9.1.

Step 2 — Define asynchronous (one sentence): Take it straight from Definition 6.9.3 and the comparison table.

Answer:

  • Synchronous: code that runs to completion before anything else can happen.
  • Asynchronous: work handed to the browser to run later, while the current code carries straight on.

Problem 8. What do setTimeout and setInterval both take as their first argument, and what unit is the second?

Solution

Step 1 — Identify the first argument: Both functions take a callback function first — the code to run when the timer fires (fn in setTimeout(fn, ms) and setInterval(fn, ms)).

Step 2 — Identify the unit of the second argument: The delay is measured in milliseconds, so setTimeout(fn, 1500) means 1.5 real seconds — not 1500 frames.

Answer: Both take a callback function as their first argument, and the second argument is a number of milliseconds.

Problem 9. Why does the bonus-coin spawner correctly use frameCount while the respawn correctly uses setTimeout? What would go wrong if they were swapped?

Solution

Step 1 — Classify each timed event: Bonus coins are a game-world event — a spawn rate that should stay consistent with the action on screen. The respawn is a real-world wait — the player is watching the clock and expects an actual 1.5 seconds.

Step 2 — Match each to its mechanism: Frame counting (frameCount % 180) naturally speeds up and slows down with the game, which is exactly what a spawn rate wants; milliseconds (setTimeout(..., 1500)) measure wall-clock time, which is exactly what a player-timed delay wants.

Step 3 — Break it by swapping: A coin spawner on setTimeout would keep firing on real time even if the frame rate collapsed, spawning coins faster than the game world is actually progressing. A respawn on frameCount % 90 would take 90 frames however long those frames last — at a struggling 30 fps that is 3 full seconds, and the player has no idea why.

Answer: Coins use frameCount because a spawn rate belongs to the game world and should slow down when the game slows down; the respawn uses setTimeout because a player-timed delay belongs to the real world and must be a true 1.5 seconds. Swapped, coins would spawn on wall-clock time even while the game crawled, and the respawn would silently stretch well past 1.5 seconds on any machine not hitting 60 fps.

Problem 10. Write a blinking "Get Ready!" text that changes visibility every half second during a countdown.

Solution

Step 1 — Convert half a second to frames: \(0.5 \times 60 = 30\) frames, so the visibility should flip once every 30 frames — that is the condition frameCount % 30 === 0.

Step 2 — Toggle a flag and draw conditionally: Keep a boolean, flip it on every 30th frame, and only draw the text while it is true. Dropped into the §6.9.5 countdown (which already has remaining):

Editor
runs in a sandboxed frame
▶ Press Run to see the output…

Step 3 — Consider the alternative: An interval could do the toggling too (setInterval(() => showText = !showText, 500)), but doing it inside draw with frameCount keeps the blink in the same loop that renders it — and there is no interval to remember to clear.

Answer: Use if (frameCount % 30 === 0) showText = !showText; inside draw (30 frames = 0.5 s at 60 fps) and draw the "Get Ready!" text only when showText is true.

Problem 11. Why must every setInterval have a matching clearInterval, and what goes wrong without one?

Solution

Step 1 — Remember that setInterval is forever: It fires every ms milliseconds until you explicitly cancel it with clearInterval(id) — the browser will never stop it on its own.

Step 2 — Trace what happens without the clear: The callback keeps firing long after the thing it belonged to is gone. In the countdown, skipping clearInterval(countdown) means remaining keeps decrementing forever — \(-1, -2, -3, \ldots\) — quietly consuming resources and corrupting the very state the game reads. Nothing crashes, which is exactly what makes it a quiet bug.

Answer: Every setInterval needs a matching clearInterval because the interval otherwise repeats forever: the callback keeps running after it has any meaning — ticking counters into negative numbers and leaking resources — a quiet bug that never announces itself. That is why you decide where the interval gets cleared in the same breath you write it.

Problem 12. In the countdown solution, why does the interval handle the counting while draw handles the display?

Solution

Step 1 — Match each job to its natural rate: The countdown changes once per second, and an interval fires exactly once per second — a perfect match. draw runs about 60 times a second, so it is the wrong place to count; worse, starting the setInterval inside draw would create 60 new intervals every second.

Step 2 — Separate state from display: The interval owns the state: it decrements remaining and clears itself once remaining hits zero. draw owns the display: each frame it just reads whatever remaining currently holds and renders it. Neither needs to know the other's timing, and the number on screen is automatically in sync with the count.

Answer: The interval ticks at once per second — exactly how often the countdown changes — so it owns the counting (and its own clearInterval); draw runs 60 times a second, so it only reads remaining and displays it. Counting and displaying are different jobs at different rates, so each lives where its rate is natural.

Key Terms

Term Definition
Synchronous Code that runs to completion before anything else happens; the draw loop is synchronous
Asynchronous Work handed to the browser to run later, while the current code carries straight on
frameCount A read-only count of frames since the sketch started; % N === 0 fires once every N frames
Frame budget The time one frame gets — about 16 milliseconds at 60 frames a second, your code included
setTimeout(fn, ms) Runs fn once after ms milliseconds and returns immediately
setInterval(fn, ms) Runs fn every ms milliseconds until cleared
clearInterval(id) Stops a repeating timer, using the id the timer returned
Wall-clock time Real elapsed seconds, as opposed to a count of frames. Use it for anything a player is timing