Overview
The race expression is a Verse language built-in (not a UEFN device), so it lives in every Verse file without any extra using import. It belongs to Verse's structured concurrency family alongside sync, rush, and branch.
race:
BranchA() # suspending call
BranchB() # suspending call
race starts all branches at the same time. The instant one branch returns, race itself returns and every other branch is automatically cancelled — their defer blocks run, any held locks are released, and execution continues after the race block.
When to reach for it:
- A round timer vs. a player completing an objective — whichever fires first ends the round.
- A "first player to reach the dock wins" race against a storm closing in.
- Waiting for a player to interact with a device OR for them to be eliminated — handle whichever happens first.
- Any "timeout" pattern: do the real work, but give up after N seconds if it hasn't finished.
race is the right tool whenever you have two or more competing async outcomes and only the winner matters.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario — Clifftop Cove Sprint: Players spawn at a sunny clifftop dock. A 30-second countdown begins. The first player to step on the finish-line trigger plate at the cove shore wins. If the timer expires before anyone reaches the shore, the round ends in a timeout. We use race so that whichever happens first — a player triggering the plate OR the 30-second clock — immediately ends the round.
Place these devices in your UEFN level:
FinishTrigger— atrigger_deviceat the cove shoreRoundEndNotifier— a secondtrigger_deviceused to signal round-over to other devices (e.g. a cinematic sequence)
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
# Localized message helper
RoundMessage<localizes>(S : string) : message = "{S}"
cove_race_manager := class(creative_device):
# The trigger plate at the cove shore — player steps on it to finish
@editable
FinishTrigger : trigger_device = trigger_device{}
# A second trigger we activate to fire downstream devices (e.g. end-round cinematic)
@editable
RoundEndNotifier : trigger_device = trigger_device{}
# How long (seconds) before the round times out
@editable
RoundDuration : float = 30.0
# Tracks whether a player won (vs. timeout)
var PlayerWon : logic = false
# Called by the engine when the island starts
OnBegin<override>()<suspends> : void =
# Subscribe to the finish trigger so we can record the winner
FinishTrigger.TriggeredEvent.Subscribe(OnPlayerFinished)
# race: timer vs. player reaching the shore
# Whichever branch returns first wins; the other is cancelled
race:
WaitForPlayerFinish() # suspends until FinishTrigger fires
RunCountdown() # suspends for RoundDuration seconds
# We reach here only after one branch completes
if (PlayerWon?):
Print("A player reached the cove — round over, player wins!")
else:
Print("Time's up! The storm swallowed the dock.")
# Signal downstream devices regardless of who won
RoundEndNotifier.Trigger()
# Branch 1 — suspends until the finish trigger fires
WaitForPlayerFinish()<suspends> : void =
# Await blocks the current branch until the event fires once
FinishTrigger.TriggeredEvent.Await()
set PlayerWon = true
# Branch 2 — suspends for the full round duration
RunCountdown()<suspends> : void =
Sleep(RoundDuration)
# If we reach here, the timer expired before any player finished
# Event handler — called whenever the trigger fires (for any subscriber)
OnPlayerFinished(Agent : ?agent) : void =
# Unwrap the optional agent safely
if (A := Agent?):
Print("Player crossed the finish line at the cove shore!")
Line-by-line breakdown:
| Lines | What's happening |
|---|---|
@editable fields |
Wire up FinishTrigger and RoundEndNotifier in the UEFN Details panel — no bare identifiers. |
FinishTrigger.TriggeredEvent.Subscribe(OnPlayerFinished) |
Registers OnPlayerFinished to run every time the plate fires, for logging/side effects. |
race: block |
Launches WaitForPlayerFinish and RunCountdown simultaneously. |
WaitForPlayerFinish |
Calls .Await() on the trigger's event — this suspends the branch until the event fires exactly once. |
RunCountdown |
Calls Sleep(RoundDuration) — suspends for 30 seconds. |
After race: |
Exactly one branch returned; the other was cancelled. We check PlayerWon to decide the message. |
RoundEndNotifier.Trigger() |
Fires a second device to kick off end-round cinematics or scoring. |
Common patterns
Pattern 1 — Timeout wrapper (race a task against a deadline)
A common use of race is giving any async operation a hard deadline. Here a player must interact with a glowing buoy device within 10 seconds or the opportunity is lost.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
buoy_interaction_manager := class(creative_device):
@editable
BuoyButton : button_device = button_device{}
@editable
MissedOpportunityNotifier : trigger_device = trigger_device{}
var InteractionSucceeded : logic = false
OnBegin<override>()<suspends> : void =
race:
WaitForBuoyPress()
TimeoutBranch()
if (InteractionSucceeded?):
Print("Buoy activated — treasure chest unlocked!")
else:
Print("Too slow — the buoy sank back into the cove.")
MissedOpportunityNotifier.Trigger()
WaitForBuoyPress()<suspends> : void =
BuoyButton.InteractedWithEvent.Await()
set InteractionSucceeded = true
TimeoutBranch()<suspends> : void =
Sleep(10.0)
Pattern 2 — Race three competing events
race accepts more than two branches. Here three pressure plates are scattered across a sunny dock area; the first player to step on any plate wins the round.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
three_plate_race := class(creative_device):
@editable
PlateA : trigger_device = trigger_device{}
@editable
PlateB : trigger_device = trigger_device{}
@editable
PlateC : trigger_device = trigger_device{}
@editable
WinnerAnnouncer : trigger_device = trigger_device{}
# Which plate index won (1, 2, or 3)
var WinningPlate : int = 0
OnBegin<override>()<suspends> : void =
race:
AwaitPlate(PlateA, 1)
AwaitPlate(PlateB, 2)
AwaitPlate(PlateC, 3)
Print("Plate {WinningPlate} was reached first!")
WinnerAnnouncer.Trigger()
AwaitPlate(Plate : trigger_device, Index : int)<suspends> : void =
Plate.TriggeredEvent.Await()
set WinningPlate = Index
Pattern 3 — Race with player elimination check
Race a player completing a shore objective against them being eliminated. If they're eliminated first, cancel the objective gracefully.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /Verse.org/Native }
shore_objective_manager := class(creative_device):
@editable
ShoreFinishTrigger : trigger_device = trigger_device{}
@editable
ObjectiveCompleteNotifier : trigger_device = trigger_device{}
@editable
PlayerSpawner : player_spawner_device = player_spawner_device{}
var ObjectiveComplete : logic = false
OnBegin<override>()<suspends> : void =
# Wait for a player to spawn before starting the objective race
SpawnedAgent := PlayerSpawner.SpawnedEvent.Await()
if (Agent := SpawnedAgent?,
FortChar := Agent.GetFortCharacter[]):
race:
WaitForShoreFinish()
WaitForElimination(FortChar)
if (ObjectiveComplete?):
Print("Player reached the shore — objective complete!")
ObjectiveCompleteNotifier.Trigger()
else:
Print("Player was eliminated before reaching the shore.")
WaitForShoreFinish()<suspends> : void =
ShoreFinishTrigger.TriggeredEvent.Await()
set ObjectiveComplete = true
WaitForElimination(Char : fort_character)<suspends> : void =
Char.EliminatedEvent().Await()
Gotchas
1. Both branches must be <suspends> calls.
If a branch returns immediately (no Sleep, no .Await()), race completes instantly on that branch and the other branch never gets a chance to run. Always make sure every branch actually suspends.
2. Cancelled branches run their defer blocks.
If a losing branch has defer statements (for cleanup), those will execute when the branch is cancelled. This is usually what you want, but be aware: don't put side-effect logic you only want on success inside a defer in a race branch.
3. race is not the same as sync.
syncwaits for all branches to finish.racewaits for one branch and cancels the rest. Usingsyncwhen you meantracemeans your round never ends until both the timer AND the player finish — a classic bug.
4. Shared mutable state needs care.
Both branches can write to the same var field. In Pattern 1 and the Walkthrough, the winning branch sets a flag (PlayerWon, InteractionSucceeded) and the losing branch never gets to set it because it's cancelled. This is safe, but if both branches could theoretically complete at the exact same simulation tick, the last writer wins — design your flags accordingly.
5. race only works inside a <suspends> context.
You cannot call race from a plain (non-suspending) function or from a Subscribe handler directly. Put your race block inside OnBegin<override>()<suspends> or another <suspends> function.
6. Localized text for messages.
If you pass text to a device method that expects a message type, you cannot pass a raw string. Declare a localizer helper:
MyLabel<localizes>(S : string) : message = "{S}"
There is no StringToMessage function in Verse.