Overview
rush is a Verse concurrent-flow expression that runs two or more <suspends> (async) blocks simultaneously and completes only when every branch has finished. It is the counterpart to race (which completes when the first branch finishes).
Use rush when:
- You need a timer counting down while a cinematic sequence plays, and the game should only continue once both are done.
- You want to award points to multiple players in parallel rather than one after another.
- You need to teleport a player and start a score animation at the same moment, waiting for both to settle before moving on.
Key rule: every branch of a rush must be an async expression (a call to a <suspends> function, or an Await on a listenable). The whole rush block itself is <suspends>, so it must live inside another async context such as OnBegin.
API Reference
(API surface could not be resolved for this device.)
Walkthrough
Scenario — Clifftop Dock Sprint Challenge
A player steps onto a pressure plate at the top of a sun-drenched clifftop. Two things must happen at the same time:
- A countdown timer starts (the player has 30 seconds to reach the dock below).
- A cinematic sequence plays — a dramatic cel-shaded swooping camera over the cove.
Only after both the cinematic finishes and the timer resolves (success or failure) does the game award points and teleport the player to the respawn pad.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
# CliftopRushDevice — place this Verse device on your island alongside
# a timer_device, cinematic_sequence_device, score_manager_device,
# teleporter_device, and player_spawner_device.
cliftop_rush_device := class(creative_device):
# Wire these up in the UEFN Details panel
@editable
RaceTimer : timer_device = timer_device{}
@editable
IntroSequence : cinematic_sequence_device = cinematic_sequence_device{}
@editable
ScoreManager : score_manager_device = score_manager_device{}
@editable
DockTeleporter : teleporter_device = teleporter_device{}
@editable
RespawnPad : player_spawner_device = player_spawner_device{}
# Called once when the island starts
OnBegin<override>()<suspends>:void =
# Wait for the timer's SuccessEvent — that fires when a player
# reaches the dock trigger that calls RaceTimer.Stop(Agent)
RaceTimer.SuccessEvent.Await()
# (In a real island you'd subscribe to a trigger at the dock
# that calls HandleDockReached; here we demo the rush pattern.)
# Called externally (e.g. from a trigger_device subscription) when
# a player steps onto the clifftop pressure plate.
StartChallenge(Agent : agent) : void =
spawn { RunChallengeAsync(Agent) }
# The core async routine — this is where rush lives.
RunChallengeAsync(Agent : agent)<suspends> : void =
# Cast agent → player so we can pass it to devices that need player
if (P := player[Agent]):
# ── RUSH: timer + cinematic run concurrently ──────────────────
# The rush block suspends until BOTH branches complete.
rush:
# Branch 1 — start the 30-second countdown for this player.
# We await either SuccessEvent or FailureEvent so the branch
# ends as soon as the timer resolves.
AwaitTimerResult(Agent)
# Branch 2 — play the swooping clifftop cinematic.
# cinematic_sequence_device.Play() is fire-and-forget, so we
# pair it with an Await on StoppedEvent so the branch suspends
# until the sequence ends naturally.
AwaitCinematic(Agent)
# ── Both branches done — now award points and teleport ────────
ScoreManager.Activate(Agent)
DockTeleporter.Activate(Agent)
# Async helper: starts the timer for Agent and waits for it to finish.
AwaitTimerResult(Agent : agent)<suspends> : void =
RaceTimer.Start(Agent)
# SuccessEvent and FailureEvent both send ?agent; we await either.
rush:
RaceTimer.SuccessEvent.Await()
RaceTimer.FailureEvent.Await()
# Timer has resolved (success or failure) — branch is done.
# Async helper: plays the cinematic for Agent and waits until it stops.
AwaitCinematic(Agent : agent)<suspends> : void =
IntroSequence.Play(Agent)
# StoppedEvent sends tuple() — just await it.
IntroSequence.StoppedEvent.Await()
Line-by-line explanation
| Lines | What's happening |
|---|---|
@editable fields |
Each UEFN device is wired in the Details panel — mandatory for creative_device references. |
StartChallenge |
A plain (non-async) entry point; it spawns the async work so it doesn't block the caller. |
rush: block |
Launches AwaitTimerResult and AwaitCinematic simultaneously. Neither blocks the other. The rush suspends RunChallengeAsync until both helpers return. |
AwaitTimerResult |
Calls RaceTimer.Start(Agent) then itself uses a nested rush to await whichever of SuccessEvent/FailureEvent fires first — because only one will fire, the inner rush finishes as soon as either resolves. |
AwaitCinematic |
Calls IntroSequence.Play(Agent) (fire-and-forget), then Awaits StoppedEvent so the branch doesn't end prematurely. |
Post-rush lines |
ScoreManager.Activate(Agent) and DockTeleporter.Activate(Agent) run only after both branches are complete — guaranteed ordering. |
Common patterns
Pattern 1 — Parallel score increments for two players
Award a bonus to two players at the same instant rather than sequentially.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
dual_score_device := class(creative_device):
@editable
BonusScorer : score_manager_device = score_manager_device{}
@editable
PlayerRefA : player_reference_device = player_reference_device{}
@editable
PlayerRefB : player_reference_device = player_reference_device{}
OnBegin<override>()<suspends>:void =
# Wait for both player references to be activated before awarding.
rush:
PlayerRefA.ActivatedEvent.Await()
PlayerRefB.ActivatedEvent.Await()
# Both players are now registered — award each a bonus point.
AwardBothAsync()<br>
AwardBothAsync()<suspends>:void =
# Fire Increment for each player concurrently, then Activate.
# (Increment + Activate is the score_manager pattern for bonus points.)
rush:
GrantBonus(PlayerRefA)
GrantBonus(PlayerRefB)
GrantBonus(Ref : player_reference_device)<suspends>:void =
Ref.Activate() # triggers the device's relay logic
Sleep(0.0) # yield so both branches truly overlap
Pattern 2 — Teleport + respawn simultaneously
Send a player through a rift while a fresh spawn pad activates, so they land immediately.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
rift_respawn_device := class(creative_device):
@editable
ShoreRift : teleporter_device = teleporter_device{}
@editable
DockSpawn : player_spawner_device = player_spawner_device{}
OnBegin<override>()<suspends>:void =
# Subscribe to the rift's EnterEvent so we react each time someone steps in.
ShoreRift.EnterEvent.Subscribe(OnPlayerEnterRift)
OnPlayerEnterRift(Agent : agent) : void =
spawn { DoRiftAndSpawn(Agent) }
DoRiftAndSpawn(Agent : agent)<suspends>:void =
if (P := player[Agent]):
rush:
# Branch 1: teleport the player to the destination group.
TeleportAsync(Agent)
# Branch 2: enable the dock spawn pad so it's ready on arrival.
EnableSpawnAsync(P)
TeleportAsync(Agent : agent)<suspends>:void =
ShoreRift.Activate(Agent)
# Await confirmation the player emerged from the rift.
ShoreRift.TeleportedEvent.Await()
EnableSpawnAsync(P : player)<suspends>:void =
DockSpawn.Enable()
DockSpawn.SpawnPlayer(P)
Sleep(0.0) # yield so branch lifetime overlaps with TeleportAsync
Pattern 3 — Timer urgency + cinematic pause, then score
When the timer enters urgency mode, pause the cinematic and increment the score at the same time.
using { /UnrealEngine.com/Temporary/UI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/Patchwork }
using { /Verse.org/Simulation }
urgency_pause_device := class(creative_device):
@editable
CoveTimer : timer_device = timer_device{}
@editable
CoveSequence : cinematic_sequence_device = cinematic_sequence_device{}
@editable
ThrillScore : score_manager_device = score_manager_device{}
OnBegin<override>()<suspends>:void =
CoveTimer.StartUrgencyModeEvent.Subscribe(OnUrgency)
OnUrgency(MaybeAgent : ?agent) : void =
if (A := MaybeAgent?):
spawn { HandleUrgencyAsync(A) }
HandleUrgencyAsync(Agent : agent)<suspends>:void =
# Pause the cinematic AND increment the score at the exact same moment.
rush:
PauseCinematicAsync(Agent)
IncrementScoreAsync(Agent)
PauseCinematicAsync(Agent : agent)<suspends>:void =
CoveSequence.Pause(Agent)
Sleep(0.0)
IncrementScoreAsync(Agent : agent)<suspends>:void =
ThrillScore.Increment(Agent)
ThrillScore.Activate(Agent)
Sleep(0.0)
Gotchas
1. rush vs race — know which you need
rush waits for all branches to finish. race waits for the first branch to finish and cancels the rest. Use race when you want "whichever happens first wins" (e.g., player reaches goal OR timer expires). Use rush when every branch must complete before you proceed.
2. Every branch must be async
Each expression inside a rush block must be a <suspends> call or an Await. A plain void call compiles but returns instantly, making that branch a no-op for concurrency purposes. If you need a synchronous side-effect in a branch, wrap it in a tiny helper that calls Sleep(0.0) to yield.
3. rush itself is <suspends>
The entire rush block is an async expression. It must live inside a <suspends> function (like OnBegin or a helper you spawn). You cannot call rush at the top level of a non-suspending function.
4. Unwrap ?agent before device calls
SuccessEvent and FailureEvent on timer_device send ?agent (optional agent). Always unwrap: if (A := MaybeAgent?): before passing to device methods that expect agent.
5. listenable(?agent) — Await returns the optional
When you Await an event typed listenable(?agent), the result is ?agent. Store it and unwrap:
MaybeAgent := CoveTimer.SuccessEvent.Await()
if (A := MaybeAgent?):
ScoreManager.Activate(A)
6. Localized text for message params
If any device you call takes a message parameter (e.g., display text on a player_reference_device), you cannot pass a raw string. Declare a localizer:
MyLabel<localizes>(S:string):message = "{S}"
and pass MyLabel("Dock Sprint!"). There is no StringToMessage function.
7. int ↔ float is not automatic
timer_device.Start and score operations use typed values. If you compute a duration as int, cast explicitly: Float := float(MyInt). Verse will not silently coerce numeric types.