bevyengine/bevy · critical

An elapsed time getter has not been provided to `Instant`. P

Error message

An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`

What it means

bevy_platform's fallback `Instant::now` reads a per-architecture monotonic counter via `unset_getter` (crates/bevy_platform/src/time/fallback.rs:151): `rdtsc` on x86/x86_64, `cntvct_el0` on aarch64. On every other architecture the `_` arm panics with this message — the only supported path there is to install a platform-specific elapsed-time getter with `Instant::set_elapsed(...)` before the first `now()` call.

Source

Thrown at crates/bevy_platform/src/time/fallback.rs:177

        }
        #[cfg(target_arch = "x86_64")] => {
            // SAFETY: standard technique for getting a nanosecond counter on x86_64
            let nanos = unsafe {
                core::arch::x86_64::_rdtsc()
            };
            Duration::from_nanos(nanos)
        }
        #[cfg(target_arch = "aarch64")] => {
            // SAFETY: standard technique for getting a nanosecond counter of aarch64
            let nanos = unsafe {
                let mut ticks: u64;
                core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks);
                ticks
            };
            Duration::from_nanos(nanos)
        }
        _ => {
            panic!("An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`")
        }
    }
}

View on GitHub (pinned to 396ca72708)

Solutions

  1. Before any time use, register a monotonic getter: `Instant::set_elapsed(|| read_cycle_counter_as_duration())`
  2. Prefer enabling a real time source for the target (std `Instant` or the web time feature) instead of the fallback
  3. If the platform has no counter, build without the code paths that call `Instant::now()`

Example fix

// before: riscv no_std target, first Instant::now() panics
// after: install a cycle-counter-based getter at startup
Instant::set_elapsed(|| {
    let ticks: u64;
    unsafe { core::arch::asm!("csrr {0}, cycle", out(reg) ticks) };
    Duration::from_nanos(ticks / CLOCKS_PER_SEC_NANO) // scaled to nanos
});
let now = Instant::now();
Defensive patterns

Strategy: validation

Validate before calling

// compile-time guard for fallback-time targets
#[cfg(all(
    not(target_arch = "x86"),
    not(target_arch = "x86_64"),
    not(target_arch = "aarch64"),
    not(feature = "web")
))]
compile_error!("bevy_platform fallback Instant needs Instant::set_elapsed on this arch");

// runtime: install the getter before anything calls now()
Instant::set_elapsed(read_monotonic_counter);

Prevention

When it happens

Trigger: Compiling with the fallback time implementation (no_std, web feature off, std time unavailable) for a target that is not x86, x86_64, or aarch64 — e.g. riscv32/riscv64, 32-bit arm, powerpc — and touching `Instant::now()`/`Instant::update()` (Bevy's Time plugin does this at startup), without calling `Instant::set_elapsed` first.

Common situations: Embedded/no_std Bevy ports or crates depending on bevy_platform built for exotic targets; CI cross-compile checks that actually run tests on riscv/arm bare-metal; switching a no_std project from a supported to an unsupported architecture.

Related errors


AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20). Data as JSON: /api/errors/0a77bb483913636b. Report an issue: GitHub.