First Across the Line: rush keeps the losers running

In this lesson you'll learn to

  • ✓Student can choose between race and rush — rush returns the first result but lets remaining tasks continue.

Student can choose between race and rush — rush returns the first result but lets remaining tasks continue.

📖 Reference & full walkthrough: First Across the Line: rush keeps the losers running

🧩 Your capstone piece: Winner detection: rush over per-racer finish awaits crowns first place while others keep racing for podium spots


What you'll learn

In Verse, time flow is a first-class language feature. Normally code runs top-to-bottom, one expression after another. But games are inherently concurrent: enemies spawn, HUDs animate, and timers tick all at the same moment. The rush expression groups two or more async expressions (functions marked <suspends>, Sleep, event awaits) so they start together.

The key behavior that sets rush apart from race: when the fastest subexpression completes, the expression that follows rush is evaluated — but the slower subexpressions are not canceled. They keep running to completion. That makes rush ideal for kicking off independent background work while reacting immediately to the first result.

How it works

Think of rush like a director shouting "Action!" at several actors simultaneously. All subexpressions begin at once. As soon as one finishes, your code moves on, but the others continue in the background.

A few rules that keep rush correct:

  • rush may only appear in a <suspends> context (like OnBegin), because its subexpressions are async.
  • Every subexpression must itself be async — calling Sleep, awaiting an event, or invoking a <suspends> helper.
  • To actually accomplish something visible, our subtasks call real player-UI APIs. We reach the HUD with Player.GetPlayerUI[] (a <decides> call, so it goes in an if) and push widgets with PlayerUI.AddWidget(Widget).

We build a text_block widget, show it immediately in one subtask, then in a second subtask wait two seconds and swap the displayed text. Because both subtasks live inside rush, the immediate update and the delayed update are scheduled concurrently and neither blocks the other.

Let's build it

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

# A device that shows a HUD message, then concurrently updates it after a delay using `rush`.
rush_ui_device := class<concrete>(creative_device):

    # Async helper: show initial text on the player's HUD immediately.
    # It is <suspends> so it can be a rush subexpression; Sleep(0.0) yields once.
    ShowOnline(TextWidget:text_block)<suspends>:void =
        Print("Task 1: pushing 'System Online' to HUD")
        TextWidget.SetText(StringToMessage("System Online"))
        Sleep(0.0)  # yield to other concurrent tasks this frame
        Print("Task 1: HUD update sent")

    # Async helper: wait, then swap the HUD text. Runs concurrently with ShowOnline.
    ShowComplete(TextWidget:text_block)<suspends>:void =
        Print("Task 2: waiting 2 seconds...")
        Sleep(2.0)
        TextWidget.SetText(StringToMessage("Task Complete"))
        Print("Task 2: delayed HUD update sent")

    # Convert a plain string into a message for the text_block.
    StringToMessage<localizes>(Value:string):message = "{Value}"

    OnBegin<override>()<suspends>:void =
        # Reach the first player; GetPlayers() returns an array, array{0} is fallible.
        if (Player := GetPlayspace().GetPlayers()[0]):
            # GetPlayerUI is <decides> — call it inside the if and bind the result.
            if (PlayerUI := GetPlayerUI[Player]):
                # Build a real text widget and add it to the HUD before rushing.
                Label := text_block{DefaultText := StringToMessage("Booting...")}
                PlayerUI.AddWidget(Label)

                # rush: both subtasks start together. When the fastest (ShowOnline)
                # completes, execution continues past rush; ShowComplete keeps running.
                rush:
                    ShowOnline(Label)
                    ShowComplete(Label)

                Print("rush returned (fastest task done); slower task still finishing.")

Try it yourself

  • Add a third subtask to the rush block (for example, one that Sleep(1.0) then prints) and observe that the line after rush fires as soon as the fastest one finishes, while the others continue.
  • Swap rush for race and watch the difference: race cancels the slower tasks, so the 2-second update never appears.
  • Replace Sleep(0.0) in ShowOnline with Sleep(3.0) so it becomes the slowest. Notice the post-rush line now fires when ShowComplete finishes instead.
  • Capture a rush result by assigning it: Result := rush: ... and feed the winner into your next expression.

Recap

  • rush runs two or more async expressions concurrently and continues as soon as the fastest completes — without canceling the slower ones (unlike race).
  • It must live in a <suspends> context, and every subexpression must be async.
  • We performed the technique for real: GetPlayerUI[Player] (a <decides> call inside if) plus AddWidget and text_block.SetText to drive a live HUD from concurrent tasks.
  • Use rush for independent background work where one immediate result shouldn't wait on slower side effects.
🔒

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/sug-implementing-concurrent-logic-with-rush-expressions-in-verse
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