Class WaitForSettledFrames
A coroutine wait that holds until the game is running at a steady frame rate, so time-based work does not start in the middle of a loading hitch.
Implements
Inherited Members
Namespace: Scylla.Core.Util
Assembly: ScyllaCore.dll
Syntax
public sealed class WaitForSettledFrames : CustomYieldInstruction, IEnumerator
Remarks
Entering play mode, loading a scene or showing something for the first time costs real time: shaders compile, atlases build, layouts resolve. Unity charges that cost to the frames it happens on, capped at the Maximum Allowed Timestep in Project Settings, which is a third of a second by default. Anything driven by delta time and started before that settles is advanced by the whole of it. A three second animation can be most of the way through before its first readable frame reaches the screen.
Yielding this first avoids that. It waits for a run of consecutive normal-length frames, which is a direct measure of "the hitching has stopped" rather than a guess at how many frames that takes, and a guess is wrong in both directions: wasteful on a warm machine and too short on a cold one.
private IEnumerator Start()
{
BuildTheScreen();
yield return new WaitForSettledFrames();
PlayTheIntro();
}
The wait is always bounded. If the frame rate never settles, it gives up after TimeoutSeconds and reports TimedOut, so a slow machine gets a late start rather than no start at all. Callers that need to know can read the flag after the yield returns.
Frame lengths are measured unscaled, because a hitch is real time and has nothing to do with the game's timescale, and a paused game would otherwise wait forever. The budget is accumulated from those same frame lengths rather than from the wall clock, so it counts effort spent waiting instead of expiring while the editor sits paused.
Coroutines are the expected use, but not the only one. A caller with no coroutine can feed frame lengths to ObserveFrame(float) from its own update loop and watch IsDone, which is also how the wait is unit tested without a running player loop.
Two debug lines are written under Util, one when the wait starts holding and one when it stops, so a startup that looks stalled can be told apart from one that is genuinely slow. They are written once each per wait, never per frame.
Constructors
WaitForSettledFrames(float, int, float)
Creates a wait that completes once the frame rate has settled.
Declaration
public WaitForSettledFrames(float settledFrameSeconds = 0.05, int settledFramesRequired = 3, float timeoutSeconds = 5)
Parameters
| Type | Name | Description |
|---|---|---|
| float | settledFrameSeconds | Longest a frame may take and still count as settled. Values at or below zero fall back to the default. |
| int | settledFramesRequired | Consecutive settled frames that complete the wait. Values below one are raised to one. |
| float | timeoutSeconds | Accumulated frame time after which the wait gives up. Values at or below zero fall back to the default. |
Remarks
The defaults suit the usual case of starting an animation after a scene comes up. Pass a shorter frame budget to demand a higher steady frame rate, or more required frames to be surer the hitching is over at the cost of starting later.
Fields
DEFAULT_SETTLED_FRAMES
Consecutive settled frames required by default before the wait completes.
Declaration
public const int DEFAULT_SETTLED_FRAMES = 3
Field Value
| Type | Description |
|---|---|
| int |
DEFAULT_SETTLED_FRAME_SECONDS
Longest a frame may take, in seconds, and still count as settled. Sits above a normal frame at any usual refresh rate and well below the timestep cap a hitch is charged at.
Declaration
public const float DEFAULT_SETTLED_FRAME_SECONDS = 0.05
Field Value
| Type | Description |
|---|---|
| float |
DEFAULT_TIMEOUT_SECONDS
Longest the wait lasts by default, in accumulated frame time, before giving up.
Declaration
public const float DEFAULT_TIMEOUT_SECONDS = 5
Field Value
| Type | Description |
|---|---|
| float |
Properties
IsDone
Gets whether the wait has finished, whether because the frame rate settled or because the allowance ran out. Read this when driving the wait manually with ObserveFrame(float) rather than yielding it.
Declaration
public bool IsDone { get; }
Property Value
| Type | Description |
|---|---|
| bool |
SettledFrameSeconds
Gets the longest a frame may take, in seconds, and still count towards settling.
Declaration
public float SettledFrameSeconds { get; }
Property Value
| Type | Description |
|---|---|
| float |
SettledFramesRequired
Gets how many consecutive settled frames complete the wait.
Declaration
public int SettledFramesRequired { get; }
Property Value
| Type | Description |
|---|---|
| int |
TimedOut
Gets whether the wait ended because it ran out of budget rather than because the frame rate settled. Meaningful once the yield has returned.
Declaration
public bool TimedOut { get; }
Property Value
| Type | Description |
|---|---|
| bool |
TimeoutSeconds
Gets the accumulated frame time allowed before the wait gives up, in seconds.
Declaration
public float TimeoutSeconds { get; }
Property Value
| Type | Description |
|---|---|
| float |
keepWaiting
Indicates if coroutine should be kept suspended.
Declaration
public override bool keepWaiting { get; }
Property Value
| Type | Description |
|---|---|
| bool |
Overrides
Remarks
Unity polls this once per frame while the coroutine is suspended, and each poll folds in the frame that just elapsed. The frame guard means an extra read, from a test or from a caller inspecting the wait, cannot advance it twice.
Methods
ObserveFrame(float)
Folds one frame's length into the wait, completing it once enough consecutive frames have come in under budget or the allowance has run out.
Declaration
public void ObserveFrame(float frameSeconds)
Parameters
| Type | Name | Description |
|---|---|---|
| float | frameSeconds | Length of the frame just observed, in seconds. |
Remarks
Yielding the wait inside a coroutine calls this automatically, once per frame. Call it directly only when driving the wait from an update loop instead, supplying the frame length and reading IsDone. Doing both double-counts frames, which only makes the wait finish sooner than it should.
The run resets on any frame that overruns, because settling means consecutive good frames. A single fast frame in the middle of a stutter is not the frame rate settling.
Reset()
Clears the accumulated state so the same instance can be yielded again.
Declaration
public override void Reset()