terminalCode Example

Async tasks in s&box — coroutines, parallel tasks, and frame-by-frame animation

calendar_today May 5, 2026 schedule ~1 min read person patrickjr verified 50

Async Tasks in s&box Components

s&box async tasks run on the main thread — they behave like coroutines. Use Task.Frame() to yield one frame, Task.DelaySeconds() to wait, and Task.WhenAll() to run tasks in parallel.

Basic Async Method

CSHARP
async Task PrintAfterDelay( float seconds, string message )
{
    await Task.DelaySeconds( seconds );
    Log.Info( message );
}

Frame-by-Frame Animation (Coroutine Pattern)

CSHARP
async Task LerpScale( float duration, Vector3 target, Easing.Function ease )
{
    TimeSince elapsed = 0;
    var from = WorldScale;

    while ( elapsed < duration )
    {
        WorldScale = Vector3.Lerp( from, target, ease( elapsed / duration ) );
        await Task.Frame();
    }

    WorldScale = target;
}

// Chain animations
await LerpScale( 0.3f, Vector3.One * 1.5f, Easing.EaseOut );
await LerpScale( 0.2f, Vector3.One,        Easing.EaseIn );

Parallel Tasks

CSHARP
async Task DoMultiple()
{
    // Start both — no await yet
    var taskA = PrintAfterDelay( 1f, "One" );
    var taskB = PrintAfterDelay( 2f, "Two" );

    // Wait for both to finish
    await Task.WhenAll( taskA, taskB );
    Log.Info( "Both done" );
}

Returning Values

CSHARP
async Task<string> FetchData( string url )
{
    return await Http.RequestStringAsync( url );
}

// From synchronous code — fire and forget
_ = FetchData( "https://example.com" );

// From synchronous code — use result when ready
FetchData( "https://example.com" )
    .ContinueWith( t => Log.Info( t.Result ) );

Calling Async from Synchronous Code

CSHARP
protected override void OnEnabled()
{
    // Fire and forget — _ discards the Task
    _ = DoMultiple();
}

Checking Completion in OnUpdate

CSHARP
Task<string> _fetchTask;

protected override void OnStart()
{
    _fetchTask = FetchData( "https://example.com" );
}

protected override void OnUpdate()
{
    if ( _fetchTask?.IsCompletedSuccessfully == true )
    {
        Log.Info( _fetchTask.Result );
        _fetchTask = null;
    }
}

Safety — Destroyed Components

Component.Task helpers (e.g. Task.Frame() called via the component) auto-cancel when the GameObject becomes invalid. For manually created tasks, check validity yourself:
CSHARP
async Task SpawnWave()
{
    await Task.DelaySeconds( 3f );

    if ( !this.IsValid() ) return;  // component was destroyed while waiting

    SpawnEnemies();
}

Common Pitfall — Stacking Tasks

If a player can trigger the same async task multiple times, guard against stacking:

CSHARP
bool _isFiring;

async Task Fire()
{
    if ( _isFiring ) return;
    _isFiring = true;

    await Task.DelaySeconds( 0.5f );
    ShootBullet();

    _isFiring = false;
}
Was this helpful?