bevyengine/bevy · critical
The system ran out of unique `{}`s.
Error message
The system ran out of unique `{}`s. What it means
bevy_utils' define_atomic_id! macro (crates/bevy_utils/src/atomic_id.rs) hands each render-resource handle type — BufferId, TextureId, TextureViewId, SamplerId, BindGroupId, BindGroupLayoutId, RenderPipelineId, ComputePipelineId, ShaderId — unique ids from a per-type static AtomicU32 starting at 1. After 2^32-1 allocations fetch_add wraps back to 0, NonZero::new(0) fails, and the macro panics with "The system ran out of unique `{TypeName}`s." — a leak detector for runaway resource creation.
Source
Thrown at crates/bevy_utils/src/atomic_id.rs:28
/// Note that this means the id space is process-wide, as such it may potentially be exhausted
/// by a combination of long-running processes and multiple bevy `World`s, at which point we panic.
#[derive(::core::marker::Copy, ::core::clone::Clone, ::core::hash::Hash, ::core::cmp::Eq, ::core::cmp::PartialEq, ::core::cmp::PartialOrd, ::core::cmp::Ord, ::core::fmt::Debug)]
pub struct $atomic_id_type(::core::num::NonZero<u32>);
impl $atomic_id_type {
/// Creates a new id via fetch_add atomic on a static global.
#[expect(
clippy::new_without_default,
reason = "Implementing the `Default` trait on atomic IDs would imply that two `<AtomicIdType>::default()` equal each other. By only implementing `new()`, we indicate that each atomic ID created will be unique."
)]
pub fn new() -> Self {
use ::core::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(1);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
Self(::core::num::NonZero::<u32>::new(counter).unwrap_or_else(|| {
::core::panic!(
"The system ran out of unique `{}`s.",
::core::stringify!($atomic_id_type)
);
}))
}
}
impl ::core::convert::From<$atomic_id_type> for ::core::num::NonZero<u32> {
fn from(value: $atomic_id_type) -> Self {
value.0
}
}
impl ::core::convert::From<::core::num::NonZero<u32>> for $atomic_id_type {
fn from(value: ::core::num::NonZero<u32>) -> Self {
Self(value)
}
}View on GitHub (pinned to 396ca72708)
Solutions
- Audit for per-frame resource creation (new buffers/textures/pipelines in systems or render worlds) and hoist them: create once, store the handle, reuse.
- Use Bevy's asset system (Assets<T>, Res) for images/shaders so handles are shared rather than freshly minted.
- Cache pipelines and bind groups in resources keyed by their layout/config instead of rebuilding them.
- Restart the process as a stopgap while you fix the leak — the counter never resets within a process.
- Instrument with a counter around your resource-creation path to confirm the growth rate before assuming exhaustion.
Example fix
// before: mints a new TextureId-backed image every frame -> eventual exhaustion
fn particles(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
commands.spawn(ImageNode::new(images.add(make_noise_image())));
}
// after: build the image once, keep the handle in a resource, reuse it
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
commands.insert_resource(NoiseTexture(images.add(make_noise_image())));
} Defensive patterns
Strategy: validation
Validate before calling
// wrap resource creation in a budgeted helper and track the growth rate
use std::sync::atomic::{AtomicU64, Ordering};
static TEXTURES_MINTED: AtomicU64 = AtomicU64::new(0);
fn mint_texture(images: &mut Assets<Image>, image: Image) -> Handle<Image> {
let n = TEXTURES_MINTED.fetch_add(1, Ordering::Relaxed);
debug_assert!(n < 4_000_000_000, "approaching u32 id exhaustion — audit for leaks");
images.add(image)
} Try / catch
// Fatal by design once ids wrap; there is nothing to catch. Detect the leak before // exhaustion by monitoring creation counters / GPU memory in your telemetry.
Prevention
- Never allocate buffers/textures/pipelines per frame — create once, store handles, update in place.
- Use Assets<T> and cached pipeline/bind-group resources keyed by config.
- Watch GPU memory and resource counts in profilers; linear growth is the early symptom.
- Restart-based mitigations (orchestrated process recycling) only buy time — fix the leak.
When it happens
Trigger: Allocating more than 4,294,967,294 instances of a single id type in one process: e.g. creating a new Buffer, GpuImage, Shader, or pipeline object every frame for ~12+ hours at 100/s, or per-entity resource creation in large scenes.
Common situations: Long-running simulations or servers that instantiate render resources per frame/per entity instead of reusing handles; particle systems generating a texture per particle; shader hot-reload loops leaking pipelines; benchmarks that loop for days.
Related errors
- Failed to insert attribute. Invalid attribute format for {}.
- Attempted to insert invalid value of type {}.
- Attempted to push invalid value of type {}.
- Attempted to insert invalid value of type {}.
- Attempted to push invalid value of type {}.
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/19893259b300467e.
Report an issue: GitHub.