Racing a Dynamic Number of Tasks: the Event-Aggregation Pattern
In Blueprints, waiting for "whichever of N buttons gets pressed first" means binding an event on each button and arbitrating with a shared flag. Verse's race is more direct, but its arm count is locked in when you write the code — and how many there are isn't known until the game is running. This page collects the community's battle-tested standard solution — spawn sentinels + event aggregation — and clears up a syntax trap that will crash the editor while we're at it.
1. The Problem: race Doesn't Eat Runtime Arrays
In the main lesson, every race arm was wired into the graph by hand: two arms, three arms — countable before you ever hit play. But suppose your level has a row of quiz-buzzer buttons whose count the level designer freely adjusts (that array variable has the Instance Editable eye lit up, so it's changeable in Details) — "wait for whichever of this row gets pressed first" means how many arms there are is only revealed once the game runs, and the race pattern jams immediately.
In the forum thread "How to (race) await an array of tasks?", the OP tried something that looks perfectly reasonable: race(T : Tasks){ T.Await() } — hoping to fan every task in the array out into its own race arm, ForEach-style. The result was even spicier: the half of the compiler that checks your writing actually waves it through, but the half that turns it into runnable logic was never built, so it takes the whole editor down (confirmed crash on 24.30). Epic engine programmer Andrew Scheidecker confirmed in the thread that this syntax is "intended to be supported, but only the first half is done so far". The takeaway is blunt: don't touch this syntax for now — wait for the official implementation. One more landmine in the same family: feeding a range expression (like 0..10, the run of numbers from 0 to 10) to sync / race / rush as an argument crashes the project every time — steer around that one too.
2. The Solution: One Sentinel per Button — Whoever Gets Pressed Calls It In
Since race's arm count is fixed, flip the problem around: don't make the main flow race N tasks — assign each button its own spawn sentinel. A sentinel's job has exactly two steps: Await (wait on) the button it watches; the moment that button is pressed, Signal its own index onto a shared "Event Dispatcher", an event(int) — Signal is like Calling that dispatcher once with an integer along for the ride (event and its Signal / Await come from /Verse.org/Concurrency). The main flow doesn't care how many sentinels exist — it just Awaits (waits on) that dispatcher, the way you'd Bind an event and wait for it to fire; the first Signal to arrive wakes it up, "which button it was" already in hand.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
which_button_device := class(creative_device):
@editable
Buttons:[]button_device = array{}
FirstPressed:event(int) = event(int){}
WatchButton(Index:int, Button:button_device)<suspends>:void =
Button.InteractedWithEvent.Await()
FirstPressed.Signal(Index)
AwaitAnyButton()<suspends>:int =
for (Index -> Button : Buttons):
spawn{ WatchButton(Index, Button) }
FirstPressed.Await()
OnBegin<override>()<suspends>:void =
Winner := AwaitAnyButton()
Print("Button {Winner} was pressed first")
Reading the graph: Buttons is a button-array variable with Instance Editable lit up (@editable) — drag in however many buttons you place in the level. FirstPressed is an "Event Dispatcher with an integer payload" (event(int)) — Signaling it is like shouting at it with a number attached. WatchButton is each button's sentinel function: it Awaits (waits on) the button it watches, and the moment it's pressed, Signals (shouts) its own index to FirstPressed. AwaitAnyButton uses a ForEach to spawn one sentinel per button, then itself Awaits FirstPressed just once — the first sentinel to shout wakes it, index in hand. OnBegin (Event BeginPlay) calls it, then Print String reports the result.
Step through it piece by piece. for (Index -> Button : Buttons) is a ForEach Loop that hands you both the index and the button itself each lap, spawning one sentinel per button — note the spawn body holds a single function call, fully legal. FirstPressed.Await() is the last node in AwaitAnyButton, and the index it receives (the Index a sentinel Signaled out) wires straight into the function's output pin — that's Verse's "implicit return": the last node's output is the return value, no dedicated Return node to hook up. Don't forget to drag the level's buttons into the Buttons array in the UEFN Details panel (that Instance Editable eye — next lesson covers it in depth), or the array stays empty and not one sentinel gets deployed.
3. Edges and Refinements of the Pattern
Two edge cases deserve attention. First, the sentinels are spawned — in the main lesson's words, they're horses off the reins, and they won't be cancelled when that stretch of graph ends: after the main flow gets its winner, the other sentinels are still stuck waiting on their Awaits. In most scenarios that's harmless (if another button gets pressed later, the Signal just echoes with nobody to catch it); but if you want a clean shutdown, give each sentinel an internal race: one wire waits for the button, the other waits for a "wrap-it-up event", and the main flow gives that event a Signal once it has its result — exactly the DIY-cancellation pattern covered in the deep-dive page "Cancellation and defer".
Second, a small twist on the same idea gives you a "dynamic sync": to wait for all tasks, have each sentinel bump a counter variable when it finishes, and Signal a "done event" once the counter fills up — the main flow still Awaits exactly once. An event is like a broadcast tower (really just an Event Dispatcher): the task count can change all it likes, but the main flow only ever listens to one channel. It's also the community's general recipe for the clash between "the count is fixed when the graph is wired" and "the real count only shows up at runtime".
You want to wait for "whichever of an arbitrary number of buttons gets pressed first". On the current version, the correct approach is?
This page is distilled from the Epic developer forums: How to (race) await an array of tasks? ↗ (includes an Epic engineer's confirmation of the race-over-array syntax's implementation status).