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#[derive(Debug, PartialEq, Eq, Clone)]
10pub enum ErrorContext {
11 System {
13 name: DebugName,
15 last_run: Tick,
17 },
18 RunCondition {
20 name: DebugName,
22 last_run: Tick,
24 system: DebugName,
26 on_set: bool,
28 },
29 Command {
31 name: DebugName,
33 },
34 Observer {
36 name: DebugName,
38 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 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 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
104pub type ErrorHandler = fn(BevyError, ErrorContext);
106
107#[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#[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#[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 #[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#[track_caller]
163#[inline]
164pub fn error(error: BevyError, ctx: ErrorContext) {
165 inner!(log::error, error, ctx);
166}
167
168#[track_caller]
170#[inline]
171pub fn warn(error: BevyError, ctx: ErrorContext) {
172 inner!(log::warn, error, ctx);
173}
174
175#[track_caller]
177#[inline]
178pub fn info(error: BevyError, ctx: ErrorContext) {
179 inner!(log::info, error, ctx);
180}
181
182#[track_caller]
184#[inline]
185pub fn debug(error: BevyError, ctx: ErrorContext) {
186 inner!(log::debug, error, ctx);
187}
188
189#[track_caller]
191#[inline]
192pub fn trace(error: BevyError, ctx: ErrorContext) {
193 inner!(log::trace, error, ctx);
194}
195
196#[track_caller]
198#[inline]
199pub fn ignore(_: BevyError, _: ErrorContext) {}