Reference Verse compiles

sync: Running Two Async Tasks at the Same Time

When a pirate ship sails into the lagoon, you want the cannon-fire HUD message AND the trigger countdown to run at the same time — not one after the other. Verse's `sync` expression is the tool for that: it launches two or more async branches in parallel and waits until every branch finishes before moving on. Master `sync` and your island logic stops feeling sequential and starts feeling alive.

Updated Examples verified on the live UEFN compiler

Overview

sync is a Verse concurrency expression that runs two or more <suspends> branches simultaneously and only continues past the sync block once every branch has completed. It is the parallel counterpart to race (which stops as soon as the first branch finishes).

The game problem it solves: Many island moments need several things to happen at the same time. A player steps onto a dock pressure plate → you want to:

  • Show a HUD countdown message that auto-hides after a few seconds, AND
  • Run a trigger cooldown loop that limits how often the plate can fire

Without sync you'd have to chain those sequentially, making the second thing wait for the first. With sync both branches run in parallel and the code after the sync block only executes once both are truly done.

When to reach for it:

  • Two independent timers that must both expire before the next phase starts
  • Showing a HUD message while simultaneously waiting for a trigger reset
  • Animating a cinematic AND waiting for a player input at the same time
  • Any "all of the above must finish" parallel gate

API Reference

trigger_device

Used to relay events to other linked devices.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse). Inherited members are merged from trigger_base_device.

trigger_device<public> := class<concrete><final>(trigger_base_device):

Events (subscribe a handler to react):

Event Signature Description
TriggeredEvent TriggeredEvent<public>:listenable(?agent) Signaled when an agent triggers this device. Sends the agent that used this device. Returns false if no agent triggered the action (ex: it was triggered through code).

Methods (call these to make the device act):

Method Signature Description
Trigger Trigger<public>(Agent:agent):void Triggers this device with Agent being passed as the agent that triggered the action. Use an agent reference when this device is setup to require one (for instance, you want to trigger the device only with a particular agent.
Trigger Trigger<public>():void Triggers this device, causing it to activate its TriggeredEvent event.
Enable Enable<public>():void Enables this device.
Disable Disable<public>():void Disables this device.
SetMaxTriggerCount SetMaxTriggerCount<public>(MaxCount:int):void Sets the maximum amount of times this device can trigger. * 0 can be used to indicate no limit on trigger count. * MaxCount is clamped between [0,20].
GetMaxTriggerCount GetMaxTriggerCount<public>()<transacts>:int Gets the maximum amount of times this device can trigger. * 0 indicates no limit on trigger count.
GetTriggerCountRemaining GetTriggerCountRemaining<public>()<transacts>:int Returns the number of times that this device can still be triggered before hitting GetMaxTriggerCount. Returns 0 if GetMaxTriggerCount is unlimited.
SetResetDelay SetResetDelay<public>(Time:float):void Sets the time (in seconds) after triggering, before the device can be triggered again (if MaxTrigger count allows).
GetResetDelay GetResetDelay<public>()<transacts>:float Gets the time (in seconds) before the device can be triggered again (if MaxTrigger count allows).
SetTransmitDelay SetTransmitDelay<public>(Time:float):void Sets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.
GetTransmitDelay GetTransmitDelay<public>()<transacts>:float Gets the time (in seconds) which must pass after triggering, before this device informs other external devices that it has been triggered.

player

Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse). Inherited members are merged from agent.

player<native><public> := class<unique><persistent><module_scoped_var_weak_map_key><epic_internal>(agent):

hud_message_device

Used to show custom HUD messages to one or more agents.

Full public surface, resolved verbatim from the live Epic digest (Fortnite.digest.verse).

hud_message_device<public> := class<concrete><final>(creative_device_base):

Events (subscribe a handler to react):

Event Signature Description
ShowMessageEvent ShowMessageEvent<public>:listenable(agent) Called when a Message has been Shown on-screen. Returns an Agent if it was Shown on a specified Agent's screen.
HideMessageEvent HideMessageEvent<public>:listenable(agent) Called when a Message has been Hidden on-screen. Returns an Agent if it was Hidden from a specified Agent's screen.
ClearAllMessagesEvent ClearAllMessagesEvent<public>:listenable(agent) Called when all queued Messages from all players that are affected by this HUD Message Device have been cleared.

Methods (call these to make the device act):

Method Signature Description
Show Show<public>(Agent:agent):void Shows the currently set HUD Message on Agents screen. Will replace any previously active message. Use this when the device is setup to target specific agents.
Show Show<public>():void Shows the currently set Message HUD message on screen. Will replace any previously active message.
Hide Hide<public>():void Hides the HUD message.
Hide Hide<public>(Agent:agent):void Hides the currently set HUD Message on Agents screen. Use this when the device is setup to target specific agents.
Show Show<public>(Agent:agent, Message:message, ?DisplayTime:float Displays a Custom message to a specific Agent that you define.Setting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device.
Show Show<public>(Message:message, ?DisplayTime:float Displays a Custom message that you define for all PlayersSetting DisplayTime to 0.0 will display the HUD message persistently.If not defined, or less than 0.0 the message will show for the time set on the device.
SetDisplayTime SetDisplayTime<public>(Time:float):void Sets the time (in seconds) the HUD message will be displayed. 0.0 will display the HUD message persistently.
GetDisplayTime GetDisplayTime<public>()<transacts>:float Returns the time (in seconds) for which the HUD message will be displayed. 0.0 means the message is displayed persistently.
SetText SetText<public>(Text:message):void Sets the Message to be displayed when the HUD message is activated. Text is clamped to 150 characters.
ClearAllMessages ClearAllMessages<public>():void Clears all queued Messages from all players that are affected by this HUD Message Device.

Walkthrough

Scenario: The Lagoon Dock Alarm

A 2D cel-shaded pirate island. A pressure-plate trigger sits on the dock. When a player steps on it:

  1. A HUD message flashes "⚓ Dock Alarm! Pirates incoming!" for 4 seconds.
  2. The trigger is disabled for 6 seconds (cooldown) so it can't be spammed.
  3. Both things happen at the same time via sync. Only after both finish does the trigger re-enable.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Attach this creative_device to a Verse Device actor in UEFN.
# Wire DockPlate -> a trigger_device placed on the dock.
# Wire AlarmHUD  -> a hud_message_device set to show for 0 sec (we control duration in code).
lagoon_dock_alarm := class(creative_device):

    # The pressure-plate trigger on the dock
    @editable DockPlate : trigger_device = trigger_device{}

    # HUD message device — set Display Time to 0 in its properties (we drive it from code)
    @editable AlarmHUD : hud_message_device = hud_message_device{}

    # Localised message helper
    AnchorMessage<localizes>(S : string) : message = "{S}"

    # Called once when the island starts
    OnBegin<override>()<suspends> : void =
        # Limit the plate to 1 trigger per cooldown window (max 1, resets via Enable below)
        DockPlate.SetMaxTriggerCount(1)
        # Subscribe — handler fires every time the plate is stepped on
        DockPlate.TriggeredEvent.Subscribe(OnDockStepped)

    # Handler: called by TriggeredEvent — receives ?agent
    OnDockStepped(MaybeAgent : ?agent) : void =
        # Disable the plate immediately so it can't double-fire
        DockPlate.Disable()
        # Kick off the parallel alarm logic in a new async task
        spawn { RunAlarm(MaybeAgent) }

    # Async helper: runs the HUD flash and the cooldown timer in parallel
    RunAlarm(MaybeAgent : ?agent)<suspends> : void =
        # Prepare the alarm text
        AlarmHUD.SetText(AnchorMessage("⚓ Dock Alarm! Pirates incoming!"))

        # sync: both branches run at the same time.
        # We only proceed past this block when BOTH branches are done.
        sync:
            # Branch 1 — show the HUD message for 4 seconds, then hide it
            block:
                if (A := MaybeAgent?):
                    AlarmHUD.Show(A)
                else:
                    AlarmHUD.Show()
                Sleep(4.0)
                AlarmHUD.Hide()

            # Branch 2 — enforce a 6-second cooldown on the trigger
            block:
                Sleep(6.0)

        # Both branches finished — re-arm the plate
        DockPlate.SetMaxTriggerCount(1)
        DockPlate.Enable()

Line-by-line explanation

Lines What's happening
@editable DockPlate Declares the trigger_device field so UEFN can wire it to the placed device.
@editable AlarmHUD Declares the hud_message_device field.
AnchorMessage<localizes> message parameters must be localized values — this helper converts a string literal.
DockPlate.SetMaxTriggerCount(1) Caps the plate at 1 fire per enable cycle.
DockPlate.TriggeredEvent.Subscribe(OnDockStepped) Wires the event to our handler.
OnDockStepped(MaybeAgent : ?agent) TriggeredEvent is listenable(?agent) so the param is ?agent.
DockPlate.Disable() Prevents re-entry while the alarm is running.
spawn { RunAlarm(MaybeAgent) } Launches the async alarm logic without blocking OnDockStepped.
sync: The key expression. Everything inside runs in parallel.
Branch 1 Shows the HUD (per-agent if we have one, global otherwise), waits 4 s, hides it.
Branch 2 Waits 6 s (the longer cooldown).
After sync Re-arms the plate — only reached once BOTH branches are done (i.e., after 6 s).

Common patterns

Pattern 1 — Querying trigger state before a sync cooldown

Check how many triggers remain and log it to a HUD, then run a dual-branch sync that resets the trigger and updates the display.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Shows remaining trigger count in HUD, then runs a sync cooldown + display clear.
lagoon_trigger_inspector := class(creative_device):

    @editable InspectTrigger : trigger_device = trigger_device{}
    @editable StatusHUD      : hud_message_device = hud_message_device{}

    CountMsg<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        # Give the trigger a max of 5 uses with a 2-second reset delay
        InspectTrigger.SetMaxTriggerCount(5)
        InspectTrigger.SetResetDelay(2.0)
        InspectTrigger.TriggeredEvent.Subscribe(OnInspect)

    OnInspect(MaybeAgent : ?agent) : void =
        spawn { ShowCountAndCooldown(MaybeAgent) }

    ShowCountAndCooldown(MaybeAgent : ?agent)<suspends> : void =
        # Read current state
        Remaining := InspectTrigger.GetTriggerCountRemaining()
        MaxCount  := InspectTrigger.GetMaxTriggerCount()
        ResetSec  := InspectTrigger.GetResetDelay()

        # Build a status string and push it to the HUD
        StatusHUD.SetText(CountMsg("Triggers left — check your HUD!"))
        if (A := MaybeAgent?):
            StatusHUD.Show(A)
        else:
            StatusHUD.Show()

        # sync: hide the HUD after 3 s AND wait for the reset delay simultaneously
        sync:
            block:
                Sleep(3.0)
                AlarmHide(MaybeAgent)
            block:
                Sleep(ResetSec)

    AlarmHide(MaybeAgent : ?agent) : void =
        if (A := MaybeAgent?):
            StatusHUD.Hide(A)
        else:
            StatusHUD.Hide()

Pattern 2 — Transmit-delay inspection + parallel HUD flash

Read GetTransmitDelay and GetDisplayTime, then use sync so the HUD message and a transmit-delay sleep run together. This pattern is useful when you need the HUD to stay up exactly as long as the transmit delay.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# Reads the trigger's transmit delay and the HUD's display time,
# then syncs a HUD flash with the transmit window.
lagoon_transmit_sync := class(creative_device):

    @editable SignalTrigger : trigger_device      = trigger_device{}
    @editable FlashHUD      : hud_message_device  = hud_message_device{}

    TransmitMsg<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        # Configure a 3-second transmit delay on the trigger
        SignalTrigger.SetTransmitDelay(3.0)
        SignalTrigger.TriggeredEvent.Subscribe(OnSignal)

    OnSignal(MaybeAgent : ?agent) : void =
        spawn { SyncFlashWithTransmit(MaybeAgent) }

    SyncFlashWithTransmit(MaybeAgent : ?agent)<suspends> : void =
        TransmitSec := SignalTrigger.GetTransmitDelay()
        HudSec      := FlashHUD.GetDisplayTime()

        # Show a "Signal transmitting..." message
        FlashHUD.SetText(TransmitMsg("📡 Signal transmitting to the cove..."))

        # sync: HUD flash branch vs transmit-delay branch — both run in parallel
        sync:
            # Branch A: show HUD, wait for its display time, then hide
            block:
                if (A := MaybeAgent?):
                    FlashHUD.Show(A)
                else:
                    FlashHUD.Show()
                Sleep(HudSec)
                if (A := MaybeAgent?):
                    FlashHUD.Hide(A)
                else:
                    FlashHUD.Hide()

            # Branch B: wait for the full transmit delay
            block:
                Sleep(TransmitSec)

        # Only here once BOTH the HUD has hidden AND the transmit delay has elapsed
        # Safe to fire the trigger programmatically now
        SignalTrigger.Trigger()

Pattern 3 — Disable / Enable gate with a ClearAllMessages cleanup

A one-shot trigger on the clifftop: fire once, clear all HUD messages and wait for a cooldown in parallel, then re-enable.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

# One-shot clifftop trigger: fires once, clears HUD and waits cooldown in sync, then re-enables.
clifftop_oneshot := class(creative_device):

    @editable ClifftopTrigger : trigger_device     = trigger_device{}
    @editable AlertHUD        : hud_message_device = hud_message_device{}

    AlertMsg<localizes>(S : string) : message = "{S}"

    OnBegin<override>()<suspends> : void =
        ClifftopTrigger.SetMaxTriggerCount(1)
        ClifftopTrigger.TriggeredEvent.Subscribe(OnClifftopFired)

    OnClifftopFired(MaybeAgent : ?agent) : void =
        ClifftopTrigger.Disable()
        spawn { CooldownAndClear(MaybeAgent) }

    CooldownAndClear(MaybeAgent : ?agent)<suspends> : void =
        AlertHUD.SetText(AlertMsg("🏴‍☠️ Clifftop beacon lit! Retreat to the cove!"))
        if (A := MaybeAgent?):
            AlertHUD.Show(A)
        else:
            AlertHUD.Show()

        # sync: clear all messages after 5 s AND enforce an 8 s cooldown
        sync:
            block:
                Sleep(5.0)
                AlertHUD.ClearAllMessages()
            block:
                Sleep(8.0)

        # Re-arm for next round
        ClifftopTrigger.SetMaxTriggerCount(1)
        ClifftopTrigger.Enable()

Gotchas

1. sync requires every branch to be <suspends>

sync only makes sense with async branches. Each sub-expression inside sync must be (or call) a <suspends> expression. If you accidentally put a plain synchronous call as the only thing in a branch, it completes instantly — which is fine, but it means that branch doesn't actually contribute any wait time. Wrap non-suspending setup in block: alongside a Sleep if you need the branch to hold.

2. ?agent unwrap — always check before calling agent-specific methods

TriggeredEvent is listenable(?agent), so your handler receives MaybeAgent : ?agent. You must unwrap it with if (A := MaybeAgent?): before passing it to AlarmHUD.Show(A) or AlarmHUD.Hide(A). Passing a ?agent directly where an agent is expected is a compile error.

3. message parameters are NOT raw strings

hud_message_device.SetText and the Show(Agent, Message, ...) overload take a message, not a string. You cannot pass a string literal directly. Declare a localized helper:

MyText<localizes>(S : string) : message = "{S}"

Then call SetText(MyText("Hello lagoon!")). There is no StringToMessage function.

4. SetMaxTriggerCount clamps to [0, 20]

Passing a value outside [0, 20] is silently clamped. 0 means unlimited triggers — not zero triggers. If you want a one-shot trigger, pass 1.

5. spawn vs direct <suspends> call in event handlers

Event handler methods (subscribed via .Subscribe(...)) are not themselves <suspends>, so you cannot Sleep or call sync directly inside them. Always spawn { MyAsyncHelper(...) } to launch async work from a synchronous handler.

6. sync waits for the longest branch

Unlike race (which exits on the first branch to finish), sync waits for all branches. If one branch sleeps for 10 seconds and another for 2 seconds, the code after sync runs after 10 seconds. Design your branch durations intentionally — the longest branch is your bottleneck.

7. GetTriggerCountRemaining returns 0 when unlimited

If GetMaxTriggerCount() returns 0 (unlimited), then GetTriggerCountRemaining() also returns 0 — not some large number. Check GetMaxTriggerCount() first if you need to distinguish "unlimited" from "zero remaining".

Guides & scripts that use trigger_device

Step-by-step tutorials that put this object to work.

Build your own lesson with trigger_device

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →