{"record":{"id":"19893259b300467e","repo":"bevyengine/bevy","slug":"the-system-ran-out-of-unique-s","errorCode":null,"errorMessage":"The system ran out of unique `{}`s.","messagePattern":"The system ran out of unique `(.+?)`s\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bevy_utils/src/atomic_id.rs","lineNumber":28,"sourceCode":"        /// Note that this means the id space is process-wide, as such it may potentially be exhausted\n        /// by a combination of long-running processes and multiple bevy `World`s, at which point we panic.\n        #[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)]\n        pub struct $atomic_id_type(::core::num::NonZero<u32>);\n\n        impl $atomic_id_type {\n            /// Creates a new id via fetch_add atomic on a static global.\n            #[expect(\n                clippy::new_without_default,\n                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.\"\n            )]\n            pub fn new() -> Self {\n                use ::core::sync::atomic::{AtomicU32, Ordering};\n\n                static COUNTER: AtomicU32 = AtomicU32::new(1);\n\n                let counter = COUNTER.fetch_add(1, Ordering::Relaxed);\n                Self(::core::num::NonZero::<u32>::new(counter).unwrap_or_else(|| {\n                    ::core::panic!(\n                        \"The system ran out of unique `{}`s.\",\n                        ::core::stringify!($atomic_id_type)\n                    );\n                }))\n            }\n        }\n\n        impl ::core::convert::From<$atomic_id_type> for ::core::num::NonZero<u32> {\n            fn from(value: $atomic_id_type) -> Self {\n                value.0\n            }\n        }\n\n        impl ::core::convert::From<::core::num::NonZero<u32>> for $atomic_id_type {\n            fn from(value: ::core::num::NonZero<u32>) -> Self {\n                Self(value)\n            }\n        }","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_utils/src/atomic_id.rs#L10-L46","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: mints a new TextureId-backed image every frame -> eventual exhaustion\nfn particles(mut commands: Commands, mut images: ResMut<Assets<Image>>) {\n    commands.spawn(ImageNode::new(images.add(make_noise_image())));\n}\n\n// after: build the image once, keep the handle in a resource, reuse it\nfn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {\n    commands.insert_resource(NoiseTexture(images.add(make_noise_image())));\n}","handlingStrategy":"validation","validationCode":"// wrap resource creation in a budgeted helper and track the growth rate\nuse std::sync::atomic::{AtomicU64, Ordering};\nstatic TEXTURES_MINTED: AtomicU64 = AtomicU64::new(0);\n\nfn mint_texture(images: &mut Assets<Image>, image: Image) -> Handle<Image> {\n    let n = TEXTURES_MINTED.fetch_add(1, Ordering::Relaxed);\n    debug_assert!(n < 4_000_000_000, \"approaching u32 id exhaustion — audit for leaks\");\n    images.add(image)\n}","typeGuard":null,"tryCatchPattern":"// Fatal by design once ids wrap; there is nothing to catch. Detect the leak before\n// exhaustion by monitoring creation counters / GPU memory in your telemetry.","preventionTips":["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."],"tags":["rust","bevy","rendering","resource-leak","panic","id-exhaustion"],"backgroundTag":"resource-id-exhaustion","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","contentChangedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-09-09T11:17:12.671Z"}