stride3d/stride · error · Exception

NextUpdate cannot be called out of the micro-thread context.

Error message

NextUpdate cannot be called out of the micro-thread context.

What it means

NextUpdate() yields the current micro-thread's execution until just before the next physics tick. It requires the caller to be running inside a Stride micro-thread (Scheduler.CurrentMicroThread) with a SynchronizationContext, because resumption is implemented via TickAwaiter tied to that context. Calling it from a plain thread, Task, or Unity-style context throws.

Solutions

  1. Call NextUpdate() only from code running on a Stride micro-thread (e.g. inside an async Script execution / ScriptComponent async method started by the engine).
  2. For non-micro-thread code, subscribe to the simulation's tick events or poll simulation state instead of awaiting NextUpdate().
  3. If awaiting is needed elsewhere, marshal to the engine context first (e.g. via the script's scheduler) before calling.

Example fix

// before (plain thread)
Task.Run(async () => await sim.NextUpdate());
// after (inside an async Stride script)
public async Task Execute() {
    var awaiter = sim.NextUpdate(); // running on a micro-thread
}
Defensive patterns

Strategy: validation

Validate before calling

if (Scheduler.CurrentMicroThread is null || SynchronizationContext.Current is null) {
    // not on a micro-thread: do not call NextUpdate()
    return;
}
var awaiter = sim.NextUpdate();

Try / catch

try { var a = sim.NextUpdate(); } catch (Exception ex) { log.Warn("NextUpdate requires micro-thread context", ex); /* fall back to event/polling */ }

Prevention

When it happens

Trigger: Calling BepuSimulation.NextUpdate() from a regular async Task method, a background thread, a console app main method, or anywhere Scheduler.CurrentMicroThread is null or SynchronizationContext.Current is null.

Common situations: Developers trying to synchronize gameplay code with physics from a non-script context (unit tests, threads, timers) instead of from Stride script/micro-thread execution.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/650aabb3e3083098. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs:367

        Debug.Assert(body is not null, "Handle is invalid, Bepu's array indexing strategy might have changed under us");
        return body;
    }

    public StaticComponent GetComponent(StaticHandle handle)
    {
        var statics = Statics[handle.Value];
        Debug.Assert(statics is not null, "Handle is invalid, Bepu's array indexing strategy might have changed under us");
        return statics;
    }

    /// <summary>
    /// Yields execution until right before the next physics tick
    /// </summary>
    /// <returns>Task that will resume next tick.</returns>
    public TickAwaiter NextUpdate()
    {
        if (Scheduler.CurrentMicroThread is null || SynchronizationContext.Current is null)
            throw new Exception($"{nameof(NextUpdate)} cannot be called out of the micro-thread context.");
        return new TickAwaiter(_preTickRunner, Scheduler.CurrentMicroThread, SynchronizationContext.Current);
    }

    /// <summary>
    /// Yields execution until right after the next physics tick
    /// </summary>
    /// <returns>Task that will resume next tick.</returns>
    public TickAwaiter AfterUpdate()
    {
        if (Scheduler.CurrentMicroThread is null || SynchronizationContext.Current is null)
            throw new Exception($"{nameof(AfterUpdate)} cannot be called out of the micro-thread context.");
        return new TickAwaiter(_postTickRunner, Scheduler.CurrentMicroThread, SynchronizationContext.Current);
    }

    /// <summary>
    /// Whether a physics test with <paramref name="mask"/> against <paramref name="collidable"/> should be performed or entirely ignored
    /// </summary>
    /// <returns>True when it should be performed, false when it should be ignored</returns>

View on GitHub (pinned to 96fad776d2)