The Timing Machine: Concurrency in Verse
Tutorialintermediatecompiles

The Timing Machine: Concurrency in Verse

Updated intermediateCode verified

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.

Verse source files

Check your understanding

Test yourself with an interactive quiz and track your progress + earn XP — free for members.

Turn this into a guided course

Add Verse concurrency: spawn, sync, race, and loop for game timing to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.

Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit.Sign in