bevyengine/bevy · critical
Draw function {} not found for {}
Error message
Draw function {} not found for {} What it means
DrawFunctions<P>::id::<T>() (draw.rs:99) panics with 'Draw function {T} not found for {P}' when no Draw function of type T has been registered for the phase item type P. It is the panicking counterpart of get_id::<T>(), which returns Option, and its doc explicitly says it panics if the id doesn't exist.
Source
Thrown at crates/bevy_render/src/render_phase/draw.rs:101
/// Retrieves the [`Draw`] function corresponding to the `id` mutably.
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {
self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f)
}
/// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`.
pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> {
self.indices.get(&TypeId::of::<T>()).copied()
}
/// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`.
///
/// Fallible wrapper for [`Self::get_id()`]
///
/// ## Panics
/// If the id doesn't exist, this function will panic.
pub fn id<T: 'static>(&self) -> DrawFunctionId {
self.get_id::<T>().unwrap_or_else(|| {
panic!(
"Draw function {} not found for {}",
core::any::type_name::<T>(),
core::any::type_name::<P>()
)
})
}
}
/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock.
///
/// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used.
#[derive(Resource)]
pub struct DrawFunctions<P: PhaseItem> {
internal: RwLock<DrawFunctionsInternal<P>>,
}
impl<P: PhaseItem> Default for DrawFunctions<P> {
fn default() -> Self {View on GitHub (pinned to 396ca72708)
Solutions
- Register the draw function before first use: app.add_render_command::<CustomPhaseItem, DrawCustom>() in your plugin's build().
- Use get_id::<T>() and handle None gracefully instead of the panicking id().
- Ensure the plugin that initializes DrawFunctions<P> and registers commands is added before the code that requests ids (check plugin ordering).
Example fix
// before
let draw_id = draw_functions.read().id::<DrawCustomPhaseItemCommands>(); // panics if unregistered
// after
let draw_functions = draw_functions.read();
let Some(draw_id) = draw_functions.get_id::<DrawCustomPhaseItemCommands>() else {
error!("DrawCustomPhaseItemCommands not registered for this phase yet");
return;
}; Defensive patterns
Strategy: validation
Validate before calling
let draw_functions = draw_functions.read();
if draw_functions.get_id::<DrawCustom>().is_none() {
// register before first use instead of letting id() panic
error!("DrawCustom not registered for this phase");
return;
} Prevention
- Register all render commands in your plugin's build() via add_render_command before anything queries ids.
- Prefer get_id() over id() in any code that can run before registration.
- Add a smoke test that builds the app and renders one frame to catch registration-order issues.
When it happens
Trigger: Calling draw_functions.read().id::<T>() where T (typically a RenderCommandState wrapping your RenderCommand) was never added via app.add_render_command::<P, T>() or DrawFunctions::write().add(..) before the call.
Common situations: Custom PhaseItems: a plugin queues items using a DrawFunctionId from id::<DrawX>() but forgot app.add_render_command::<CustomPhaseItem, DrawX>(); initialization order where ids are requested before registration; refactors that changed the command type so registered and requested types differ.
Related errors
- `{type_path}` should be registered in type registry via `App
- Cannot call `ReflectComponent::reflect_mut` on component {na
- Cannot call `ReflectComponent::reflect_unchecked_mut` on com
- component should represent a type.
- `{type_path}` should have #[reflect(Component)] or #[reflect
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/fff5630a60593b45.
Report an issue: GitHub.