The Timing Toolbox: spawn/sync/race/loop together

In this lesson you'll learn to

  • βœ“Student can combine all four concurrency primitives into one game-timing structure and pick the right tool per job.

Student can combine all four concurrency primitives into one game-timing structure and pick the right tool per job.

πŸ“– Reference & full walkthrough: The Timing Toolbox: spawn/sync/race/loop together

🧩 Your capstone piece: The full race-loop skeleton: loop rounds, sync starts, race timeouts, spawn monitors (FAILS compile 5 err β€” the zone's worst-broken load-bearing article; intermediate sibling verse-concurrency-async-race-state-machines PASSES as backup)


The Timing Machine: Concurrency in Verse

In Fortnite islands, lots of things happen at once: a countdown ticks while lights pulse and the HUD updates. Verse handles this with concurrency β€” first-class language keywords that let you control time flow just like program flow.

What you'll learn

  • What concurrency means, and why Verse builds it into the language.
  • How spawn starts a background task without blocking the current one.
  • How race runs tasks together and stops when the first finishes, cancelling the rest.
  • How loop and Sleep drive a per-second countdown.
  • How to push live updates to a player's screen with the player UI APIs (GetPlayerUI, AddWidget, text_block).

How it works

Normally Verse runs one line after another β€” sequential execution. Concurrency lets code overlap in time. Verse's model is cooperative: tasks yield control at suspension points (like Sleep), so there are no data races or deadlocks.

The tools we use:

  • Sleep(Seconds) β€” a <suspends> call that pauses the current task, letting others run.
  • spawn β€” launches an async function in the background; the caller keeps going immediately.
  • race β€” runs two or more async blocks and stops the moment one finishes, cancelling the rest. Perfect for "count down OR bail after a timeout."
  • loop β€” repeats a block until you break.

Because these are async, they only work inside a <suspends> context β€” like OnBegin.

To actually SHOW the countdown we grab each player's UI with GetPlayerUI[Player], add a text_block inside a canvas, and rewrite its text each tick with SetText. Note GetPlayerUI[...] is fallible (uses []), so it must be called in a failure context like if. One more Verse rule: UI text APIs take a message, not a plain string β€” so we define a tiny <localizes> helper (RaceText) that converts our strings into localizable messages.

Let's build it

We'll build a Race Start Device. A player presses a button; the light turns on; a HUD countdown races against a maximum wait time; when it reaches zero the audio plays and the light turns off.

What you need in UEFN: one Button device, one Customizable Light device, and one Audio Player device. Assign each to the matching @editable field, then build.

using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# Localization helper β€” UI text must be a `message`, not a plain string.
RaceText<localizes>(S : string) : message = "{S}"

# A Race Start device: press the button to run a concurrent, on-screen countdown.
race_start_device := class<concrete>(creative_device):

    @editable StartButton : button_device = button_device{}         # Player presses to start
    @editable StartLight : customizable_light_device = customizable_light_device{}  # Turns on during countdown
    @editable GoAudio : audio_player_device = audio_player_device{}  # Plays on "GO!"

    # Entry point β€” <suspends> so we can await events and run concurrency.
    OnBegin<override>()<suspends>: void =
        # Device event: no () before .Subscribe. Start a countdown on each press.
        StartButton.InteractedWithEvent.Subscribe(OnButtonPressed)

    # Handler runs when a player presses the button.
    OnButtonPressed(Agent: agent): void =
        # spawn launches the async countdown in the background so this handler returns.
        spawn { RunCountdown(Agent) }

    # The concurrent countdown sequence for the pressing player.
    RunCountdown(Agent: agent)<suspends>: void =
        StartLight.TurnOn()                       # Light on while we count
        # Build a text widget and add it to the player's HUD.
        Label := text_block{DefaultText := RaceText("Ready...")}
        if:
            Player := player[Agent]
            UI := GetPlayerUI[Player]           # GetPlayerUI is fallible -> use if
        then:
            Canvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.2}, Maximum := vector2{X := 0.5, Y := 0.2}}
                        Widget := Label
            UI.AddWidget(Canvas)
            # race: whichever branch finishes first wins; the loser is cancelled.
            #  - CountdownLoop ticks 3..2..1 into the label, then returns
            #  - Sleep(10.0) is a safety timeout that cancels a stuck countdown
            race:
                CountdownLoop(Label)
                Sleep(10.0)
            Label.SetText(RaceText("GO!"))
            # Fire the audio and clean up the light + widget.
            GoAudio.Play(Agent)
            StartLight.TurnOff()
            Sleep(1.0)
            UI.RemoveWidget(Canvas)

    # Counts down from 3, rewriting the on-screen label every second.
    CountdownLoop(Label : text_block)<suspends>: void =
        var Remaining : int = 3
        loop:
            if (Remaining <= 0):
                break                            # Countdown done β€” leave the loop
            Label.SetText(RaceText("Countdown: {Remaining}"))
            Print("Countdown: {Remaining}")
            Sleep(1.0)                           # yield one second
            set Remaining = Remaining - 1        # use set to reassign a var```

## Try it yourself

* Change the timeout in `Sleep(10.0)` to `2.0` and watch the safety branch win the race, cancelling the countdown early.
* Show the countdown to **everyone** by looping `for (Player : GetPlayspace().GetPlayers())` and adding a widget per player.
* Add a second async block to the `race` that waits on `StartButton.InteractedWithEvent.Await(...)` so a second press aborts the countdown.
* Swap the light for a color pulse loop running inside the same `race`.

## Recap

* **Concurrency** in Verse controls time flow with language keywords β€” no threads, mutexes, or data races.
* **`spawn`** starts background work; **`race`** runs blocks together and keeps the first to finish; **`loop`** + **`Sleep`** drive per-tick timing.
* These require a `<suspends>` context like `OnBegin`.
* Real UI comes from `GetPlayerUI[Agent]` (fallible β€” call it in an `if`), `AddWidget`, a `canvas` with `text_block`, and `SetText` to update each tick β€” always feeding it a `message` built by a `<localizes>` helper.
* Reassign a `var` with `set`, and remember device events subscribe without `()` while character events need them.
πŸ”’

Keep going β€” free

You've read the intro. The rest of this lesson is free for members. Sign in to continue and track your progress.

Sign in free to continue this lesson
Pass the quiz above to chart this quest in your Journal.

Sources

/guides/verse-concurrency-spawn-sync-race-and-loop-for-game-timing
Source

Biloxi Studios original lesson

Guided course

Add this lesson to your free study plan.

🧭 The Keeper’s log

Quest complete? Chart your next heading from the πŸ¦‘ West Coves expedition.

β›΅ Take me there