Advanced · EXTRA
The Model Beneath the Flow of Time
A close read of the official Unreal Fest 2023 talk: concurrency nodes are Branch / ForEach on the time axis.
Enter the extra →In Blueprints, time flows through the Delay node and the latent nodes wearing a little clock; Verse writes that fact into the function signature — <suspends> is a function's time pass. This lesson swaps Delay for Sleep and shows what Blueprint's "no Delay inside a Function" rule turns into over in Verse.
In Blueprints, when you want something to happen three seconds later, you reach for the Delay node. Every line of Verse this site has taught you so far maps to an instant node — Set assignments, Branch checks, ForEach traversals — running start to finish within the current frame, never pausing in between. So what about Delay? In Verse it is called Sleep. And where Blueprints hint at "this node waits" with a little clock icon, Verse writes the fact straight into the function signature: the effect specifier suspends.
Last lesson we met the specifier family, and suspends belongs to its "effect specifier" branch: think of it as a label stuck onto a Blueprint function, solemnly declaring to Compile — this function crosses time. A function wearing the suspends label is an async function: it can pause (suspend) at some node along the way, hand control back to the engine for a while, then wake up in place several simulation frames later and keep going down the exec line — the same "waits a while" flavor as the latent nodes with the little clock icon in Blueprints (Delay, for instance). It works like a time pass: only functions holding one are allowed through the gate between frame and frame.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
launch_device := class(creative_device):
# suspends goes after the parameter list, before the return type
Countdown()<suspends>:void =
Print("Ready...")
Sleep(1.0)
Print("Go!")
OnBegin<override>()<suspends>:void =
# Calling an async function directly from an async context:
# OnBegin pauses on this line until Countdown has fully finished
Countdown()
Print("My turn comes only after the countdown")
Translating the graph: there are two Blueprint functions here. Countdown wires up three nodes — a Print String "Ready..." → a 1-second Delay (that's Sleep(1.0)) → a Print String "Go!". In OnBegin (the equivalent of Event BeginPlay), the exec line first plugs into Countdown; because Countdown has a Delay that really waits hanging inside, the main line waits for the whole thing to finish before the final Print String "My turn comes only after the countdown" gets its turn.
One rule to keep: an async function can only be called from an async context — either wire it inside another function wearing the suspends label, or inside a concurrency node (next lesson's spawn / race / sync). The OnBegin above is born with suspends, so it can plug its exec line straight into Countdown(); and that "direct hookup" makes OnBegin wait at that node until Countdown has run start to finish before moving on — a wait you never had to write. Note: only OnBegin's exec line stops; the game world keeps running as usual.
And with that, a cold case finally closes: the OnBegin<override>()<suspends>:void you've been copy-pasting since Lesson 6 can at last be read word for word — <override> means the same thing as that Override dropdown in Blueprints — you're overriding an entry point the parent class prepared; <suspends> says this entry point itself crosses time (little clock included). Epic designed OnBegin (that is, Event BeginPlay) as an async function precisely because they assume you'll do cross-time things — waiting, counting down, phasing — right at match start.
A pass alone won't cut it — you also need time bricks. The smallest, most-used one in Verse is Sleep. It is exactly that Delay node from Blueprints — fill in a number of seconds, and the exec line waits that long when it gets here. It lives in the /Verse.org/Simulation drawer, so write using { /Verse.org/Simulation } first to pull the drawer open (think of enabling a plugin). Sleep carries suspends itself, so "whoever uses Sleep must hold a time pass" — and the pass gets demanded link by link up the "A calls B, B calls C" chain: as soon as anyone in the chain uses a node that waits, the whole string of functions must be able to wait.
Sleep comes in three flavors, and the official docs spell them out:
| How you write it | What actually happens |
|---|---|
Sleep(1.0) |
The current exec line suspends and wakes about 1 second later, on the nearest simulation frame |
Sleep(0.0) |
Not "no sleep at all" — it suspends until the next simulation frame; the standard spelling of "yield one frame" |
Sleep(Infinity) |
Sleeps forever; it only ends when its task is canceled (losing next lesson's race, for example) |
The most misunderstood of the three is Sleep(0.0): it means "I'm done for this frame — call me again next frame." The official docs specifically recommend it: a loop that needs to work every frame should put one Sleep(0.0) in its body to yield control and avoid hogging the processor — that's the foundation of Section 4's countdown and of every per-frame loop to come.
One more instinct to correct: Sleep is not the game's pause button. It only tucks the current exec line into a sleeping bag — outside the campsite, the world keeps going: other tasks run, players move, physics ticks. You do your sleeping; the game minds its business.
How long Sleep sleeps before waking depends on one low-level concept: the simulation frame. In the official glossary, simulation update, tick, frame, and update all name the same thing — one "heartbeat" in which the engine advances the world. A Fortnite server beats roughly 30 times a second, so the smallest tick of time Verse can perceive is about 1/30 of a second. Which means Sleep(0.01) will not wake up 10 milliseconds later — the earliest it can wake is the next frame. Need high-precision timing? Sleep can't do that job. Let it go.
Drawing the line at "does it cross frames," Verse splits everything you can wire into a graph into two camps:
| Camp | Trait | Example members |
|---|---|---|
| The immediate kind (immediate) | Guaranteed to complete within the current simulation frame | Set assignments, arithmetic, Branch, ForEach, Print String |
| The async kind (async) | May take one or more simulation frames | Sleep (Delay), Await on an event, concurrency nodes like spawn / race / sync |
The split brings two hard rules. One: inside a "can this path go through" check (say, a Branch condition, or the body of one of those functions that can fail its dice roll), you may not place nodes that wait — a path that fails must roll back "as if nothing ever happened," and time already spent can't flow backward; two: <suspends> and <decides> can't both be stamped on one function, for the very same reason. Want to see where suspends sits on the full effect matrix? The extra page at the end has you covered.
Snap suspends, Sleep, and loop together and you get the most classic time structure in games — the countdown. The launch pad below calls the count once per second and lifts off at zero. Click "Run next step" and keep your eyes locked on the Sleep(1.0) Delay node: every time execution reaches it, one second of real time flows past.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
countdown_device := class(creative_device):
OnBegin<override>()<suspends>:void =
Print("Countdown started!")
var Remaining:int = 3
loop:
Print("T-minus {Remaining}...")
Sleep(1.0)
set Remaining -= 1
if (Remaining <= 0):
break
Print("Liftoff!")
Click “Run next step” to watch the code execute line by line.
"loop + Sleep + break" is the all-purpose template for time loops in Verse: loop handles repetition, Sleep makes time flow, break wraps things up. One iron law to remember: the loop body needs at least one node that waits (a Sleep-style Delay, or an Await on an event). What happens if it's missing? See the accident scene in the next section.
Pitfall one, and one of the most frequent ways in all of Verse to turn Compile red: using Sleep (that Delay which waits) inside a function that never got the suspends label.
# ✗ Compile error: Helper has no suspends, yet it calls Sleep
Helper():void =
Sleep(2.0)
Print("Wait two seconds before speaking")
The compiler throws this exact sentence at you: This invocation calls a function that has the 'suspends' effect, which is not allowed by its context. Two fixes: give the function its <suspends> label (then follow the who-calls-whom chain upward to see whether the upstream needs it too); or, if you never meant to wait for it, use next lesson's spawn to fork a new line and let it run by itself. There's also a high-frequency variant: hand-writing OnBegin and dropping <suspends> or <override> — every waiting node after it goes red in one blast, and beginners start from the node that turned red. Go back and check the labels on the entry line first!
# ✓ Fixed: time pass attached
Helper()<suspends>:void =
Sleep(2.0)
Print("Wait two seconds before speaking")
Pitfall two: writing loop: inside a suspends function with no Sleep / Await anywhere in the body. The exec line spins in place within a single frame, never handing control back to the engine — a frozen server at best, a runtime infinite-loop error at worst. House rule: every per-frame loop carries at least one Sleep(0.0).
Pitfall three: treating Sleep as a precision stopwatch. Sleep(0.01) actually waits for the next simulation frame (about 1/30 s). Pitfall four: believing Sleep pauses the whole game — it suspends only the current exec line; every other task keeps running. Pitfall five (a preview): the callback you Bind to a button event is an ordinary (can't-wait) function — no dropping a Sleep straight into it; you must use spawn to fork a new line that can wait — Lessons 24 and 25 unpack this in full.
Enough theory — come repair a broken patrol bot. It's missing two things: the function's time pass, and the yield-a-frame brick in its loop. Fill them in, then click "Check answers".
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
patrol_device := class(creative_device):
# Repair task 1: add the missing time-pass effect specifier
Patrol()<____>:void =
loop:
Print("Patrolling...")
# Repair task 2: yield control every frame (fill in the function name)
____(0.0)
OnBegin<override>()<suspends>:void =
Patrol()
"Making time flow" is something you already do every day in Blueprints — the parts are just scattered across different nodes. Here they are, lined up against the Verse spelling. The differences column is the one that matters.
| How you do it in Blueprints | How you write it in Verse | Difference |
|---|---|---|
| The Delay node, Duration in seconds | Sleep(1.0) |
Same meaning. But Sleep is an ordinary function call — it drops into a loop body or a branch without needing its own node slot |
| Latent nodes with the little clock icon (Delay, Move Component To, Play Anim…) | A function marked <suspends> |
Blueprints hint "this one waits" with an icon; Verse writes it into the signature and the compiler enforces it — up and down the whole call chain |
| Delay only works in the event graph; you cannot put one inside a custom Function | Any function can wait, as long as it is marked <suspends> |
Verse has no "event graph waits, functions don't" split; the price is that the label spreads up the who-calls-whom chain |
| Event Tick — the engine calls you once per frame | loop: with a Sleep(0.0) in the body |
Verse has no Tick event; per-frame work is hand-written. And skip it when you can — Lesson 25 shows that event-driven almost always beats per-frame polling |
| A Timeline track with a curve dragged out for interpolation | No direct counterpart: write "loop + Sleep + interpolate by elapsed time" yourself | Verse lacks the visual curve editor; in exchange the interpolation is ordinary code — reusable, diffable, version-controllable |
| Set Timer by Event, remember the handle, Clear Timer later | Make the wait a task line and govern its life with Lesson 24's race / branch |
Blueprint timer handles are yours to clean up; Verse's structured concurrency retires the task along with the scope it lives in |
The root of every difference is one thing: Blueprints hide "this waits" in a node's appearance, while Verse lifts it to the type level. So forgetting that a node is latent is usually just unexpected behavior in Blueprints, whereas in Verse it turns Compile red on the spot — Section 5's 'suspends' effect ... not allowed by its context is exactly that check speaking up.
One difference runs the other way, and it is worth remembering: re-triggering a Blueprint Delay while it is already counting down makes the new call get ignored (the editor even warns that the Delay is already in progress). Verse's Sleep has no such hidden behavior — every exec line naps on its own and none swallows another. If you want "only one of these may be running," you have to say so yourself, with next lesson's concurrency expressions.
Time pass in hand — three gates to clear for the inspection. Zero penalty for wrong answers; retry as many times as you like.
Inside an ordinary function without <suspends>, you drop in a Sleep(2.0) (a Delay). What happens?
What does Sleep(0.0) really mean?
In a boss-fight script, one exec line reaches a Sleep(5.0) (a Delay). What does the rest of the game do during those 5 seconds?
Advanced · EXTRA
A close read of the official Unreal Fest 2023 talk: concurrency nodes are Branch / ForEach on the time axis.
Enter the extra →Technique · EXTRA
Community measurements: the 30-tick ceiling, per-frame loops, and DeltaTime-free animation.
Enter the extra →Deep Dive · EXTRA
Where suspends sits in the effect matrix, and why it and decides are mutually exclusive.
Enter the extra →