Skip to main content

bevy_ecs/error/
handler.rs

1use core::fmt::Display;
2
3use crate::{change_detection::Tick, error::BevyError, prelude::Resource};
4use bevy_ecs::error::Severity;
5use bevy_utils::prelude::DebugName;
6use derive_more::derive::{Deref, DerefMut};
7
8/// Context for a [`BevyError`] to aid in debugging.
9#[derive(Debug, PartialEq, Eq, Clone)]
10pub enum ErrorContext {
11    /// The error occurred in a system.
12    System {
13        /// The name of the system that failed.
14        name: DebugName,
15        /// The last tick that the system was run.
16        last_run: Tick,
17    },
18    /// The error occurred in a run condition.
19    RunCondition {
20        /// The name of the run condition that failed.
21        name: DebugName,
22        /// The last tick that the run condition was evaluated.
23        last_run: Tick,
24        /// The system this run condition is attached to.
25        system: DebugName,
26        /// `true` if this run condition was on a set.
27        on_set: bool,
28    },
29    /// The error occurred in a command.
30    Command {
31        /// The name of the command that failed.
32        name: DebugName,
33    },
34    /// The error occurred in an observer.
35    Observer {
36        /// The name of the observer that failed.
37        name: DebugName,
38        /// The last tick that the observer was run.
39        last_run: Tick,
40    },
41}
42
43impl Display for ErrorContext {
44    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45        match self {
46            Self::System { name, .. } => {
47                write!(f, "System `{name}` failed")
48            }
49            Self::Command { name } => write!(f, "Command `{name}` failed"),
50            Self::Observer { name, .. } => {
51                write!(f, "Observer `{name}` failed")
52            }
53            Self::RunCondition {
54                name,
55                system,
56                on_set,
57                ..
58            } => {
59                write!(
60                    f,
61                    "Run condition `{name}` failed for{} system `{system}`",
62                    if *on_set { " set containing" } else { "" }
63                )
64            }
65        }
66    }
67}
68
69impl ErrorContext {
70    /// The name of the ECS construct that failed.
71    pub fn name(&self) -> DebugName {
72        match self {
73            Self::System { name, .. }
74            | Self::Command { name, .. }
75            | Self::Observer { name, .. }
76            | Self::RunCondition { name, .. } => name.clone(),
77        }
78    }
79
80    /// A string representation of the kind of ECS construct that failed.
81    ///
82    /// This is a simpler helper used for logging.
83    pub fn kind(&self) -> &str {
84        match self {
85            Self::System { .. } => "system",
86            Self::Command { .. } => "command",
87            Self::Observer { .. } => "observer",
88            Self::RunCondition { .. } => "run condition",
89        }
90    }
91}
92
93macro_rules! inner {
94    ($call:path, $e:ident, $c:ident) => {
95        $call!(
96            "Encountered an error in {} `{}`: {}",
97            $c.kind(),
98            $c.name(),
99            $e
100        );
101    };
102}
103
104/// Defines how Bevy reacts to errors.
105pub type ErrorHandler = fn(BevyError, ErrorContext);
106
107/// Fallback error handler to call when an error is not handled otherwise.
108/// Defaults to [`match_severity()`].
109///
110/// Called both for explicitly returned errors, and when a panic occurs.
111///
112/// When updated while a [`Schedule`] is running, it doesn't take effect for
113/// that schedule until it's completed.
114///
115/// [`Schedule`]: crate::schedule::Schedule
116#[derive(Resource, Deref, DerefMut, Copy, Clone)]
117pub struct FallbackErrorHandler(pub ErrorHandler);
118
119impl Default for FallbackErrorHandler {
120    fn default() -> Self {
121        Self(match_severity)
122    }
123}
124
125/// Error handler that defers to an error's [`Severity`].
126#[track_caller]
127#[inline]
128pub fn match_severity(err: BevyError, ctx: ErrorContext) {
129    match err.severity() {
130        Severity::Ignore => ignore(err, ctx),
131        Severity::Trace => trace(err, ctx),
132        Severity::Debug => debug(err, ctx),
133        Severity::Info => info(err, ctx),
134        Severity::Warning => warn(err, ctx),
135        Severity::Error => error(err, ctx),
136        Severity::Panic => panic(err, ctx),
137    }
138}
139
140/// Error handler that panics with the system error. If panics are passed as errors
141/// these will be resumed.
142#[track_caller]
143#[inline]
144#[expect(
145    clippy::allow_attributes,
146    reason = "allow is needed because this only applies for no-std"
147)]
148#[allow(unused_mut, reason = "mut is needed for std feature")]
149pub fn panic(mut error: BevyError, ctx: ErrorContext) {
150    // if the error originates from a panic, just resume unwinding
151    #[cfg(feature = "std")]
152    if matches!(error.severity(), Severity::Panic)
153        && let Some(payload) = error.take_payload()
154    {
155        std::panic::resume_unwind(payload);
156    }
157
158    inner!(panic, error, ctx);
159}
160
161/// Error handler that logs the system error at the `error` level.
162#[track_caller]
163#[inline]
164pub fn error(error: BevyError, ctx: ErrorContext) {
165    inner!(log::error, error, ctx);
166}
167
168/// Error handler that logs the system error at the `warn` level.
169#[track_caller]
170#[inline]
171pub fn warn(error: BevyError, ctx: ErrorContext) {
172    inner!(log::warn, error, ctx);
173}
174
175/// Error handler that logs the system error at the `info` level.
176#[track_caller]
177#[inline]
178pub fn info(error: BevyError, ctx: ErrorContext) {
179    inner!(log::info, error, ctx);
180}
181
182/// Error handler that logs the system error at the `debug` level.
183#[track_caller]
184#[inline]
185pub fn debug(error: BevyError, ctx: ErrorContext) {
186    inner!(log::debug, error, ctx);
187}
188
189/// Error handler that logs the system error at the `trace` level.
190#[track_caller]
191#[inline]
192pub fn trace(error: BevyError, ctx: ErrorContext) {
193    inner!(log::trace, error, ctx);
194}
195
196/// Error handler that ignores the system error.
197#[track_caller]
198#[inline]
199pub fn ignore(_: BevyError, _: ErrorContext) {}