Repository: bevyengine/bevy
File: crates/bevy_render/src/error_handler.rs (Rust)

Code before:
pub enum RenderErrorPolicy {
    /// Panics on error.
    Panic,
    #[default]
    /// Signals app exit on error.
    Shutdown,
    /// Keeps the app alive, but stops rendering further.
    StopRendering,
    /// Attempt renderer recovery the given number of times.
    Recover(usize, WgpuSettings),
}

/// The current state of the renderer.
#[derive(Resource, Debug)]
pub(crate) enum RenderState {
    /// Just started, [`crate::RenderStartup`] will run in this state.
    Initializing,
    /// Everything is okay and we are rendering stuff every frame.
    Ready,
    /// An error was encountered, and we may decide how to handle it.
    Errored(ErrorType, String, Option<WgpuWrapper<ErrorSource>>),
    /// We are recreating the render context after an error to recover.
    Reinitializing,
}

/// Resource to allows polling wgpu error handlers.
#[derive(Resource)]
pub(crate) struct RenderErrorHandler {
    device_lost: Arc<Mutex<Option<(wgpu::DeviceLostReason, String)>>>,
    uncaptured: Arc<Mutex<Option<WgpuWrapper<wgpu::Error>>>>,
}

impl RenderErrorHandler {
    /// Creates and registers error handlers on the given device and stores them to later be polled.
    pub(crate) fn new(device: &RenderDevice) -> Self {
        let device_lost = Arc::new(Mutex::new(None));
        let uncaptured = Arc::new(Mutex::new(None));
        {
            // scoped clone to move into closures
            let device_lost = device_lost.clone();
            let uncaptured = uncaptured.clone();
            let device = device.wgpu_device();
            // we log errors as soon as they are captured so they stay chronological in logs
            // and only keep the first error, as it often causes other errors downstream
            device.set_device_lost_callback(move |reason, str| {
                bevy_log::error!("Caught DeviceLost error: {reason:?} {str}");
                assert!(device_lost.lock().unwrap().replace((reason, str)).is_none());
            });
            device.on_uncaptured_error(Arc::new(move |e| {
                bevy_log::error!("Caught rendering error: {e}");
                uncaptured

Review comment (suggestion):
```suggestion
/// Resource to allow polling wgpu error handlers.
```

Code after:
pub enum RenderErrorPolicy {
    /// Pretends nothing happened and continues rendering.
    /// This discards the error after logging it to console.
    Ignore,
    /// Keeps the app alive, but stops rendering further.
    /// This keeps the error state, and will continue polling the [`RenderErrorHandler`]
    /// every frame until some other policy is returned.
    StopRendering,
    /// Attempt renderer recovery with the given [`RenderCreation`].
    Recover(RenderCreation),
}

/// Determines what [`RenderErrorPolicy`] should be used to respond to a given [`RenderError`].
///
/// The handler has access to both the main world and the render world in that order.
/// By the time this is invoked, the error has already been logged. The error is provided
/// for the decision-making reason of how to appropriately respond to it. Not all errors
/// are equally severe: validation errors may be ignored for example, while device lost errors
/// require recovery to continue rendering.
#[derive(Resource)]
pub struct RenderErrorHandler(
    pub for<'a> fn(&'a RenderError, &'a mut World, &'a mut World) -> RenderErrorPolicy,
);

impl RenderErrorHandler {
    fn handle(&self, error: &RenderError, main_world: &mut World, render_world: &mut World) {
        match self.0(error, main_world, render_world) {
            RenderErrorPolicy::Ignore => {
                // Pretend that didn't happen.
                render_world.insert_resource(RenderState::Ready);
            }
            RenderErrorPolicy::StopRendering => {
                // do nothing
            }
            RenderErrorPolicy::Recover(render_creation) => {
                assert!(insert_future_resources(&render_creation, main_world));
                render_world.insert_resource(RenderState::Reinitializing);
            }
        }
    }
}

impl Default for RenderErrorHandler {
    fn default() -> Self {
        // This is what we've always done historically,
        // but we could choose a new default once recovery works better.
        Self(|_, _, _| RenderErrorPolicy::Ignore)
    }
}

/// An error encountered during rendering.