bevy_panic_handler/
lib.rs1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
6
7use std::sync::Arc;
8
9use bevy::prelude::*;
10
11pub trait PanicHandleFn<Res>:
12 Fn(&std::panic::PanicHookInfo) -> Res + Send + Sync + 'static
13{
14}
15impl<Res, T: Fn(&std::panic::PanicHookInfo) -> Res + Send + Sync + 'static> PanicHandleFn<Res>
16 for T
17{
18}
19
20#[derive(Default)]
21pub struct PanicHandlerBuilder {
22 name: Option<Arc<dyn PanicHandleFn<String>>>,
23 body: Option<Arc<dyn PanicHandleFn<String>>>,
24 hook: Option<Arc<dyn PanicHandleFn<()>>>,
25}
26impl PanicHandlerBuilder {
27 #[must_use]
28 pub fn build(self) -> PanicHandler {
30 PanicHandler {
31 title: {
32 self.name.unwrap_or_else(|| {
33 Arc::new(|_: &std::panic::PanicHookInfo| "Fatal Error".to_owned())
34 })
35 },
36 body: {
37 self.body.unwrap_or_else(|| {
38 Arc::new(|info| {
39 format!(
40 "Unhandled panic! at {}:\n{}",
41 info.location()
42 .map_or_else(|| "Unknown Location".to_owned(), ToString::to_string),
43 info.payload().downcast_ref::<String>().map_or_else(
44 || (*info.payload().downcast_ref::<&str>().unwrap_or(&"No Info"))
45 .to_string(),
46 ToOwned::to_owned,
47 )
48 )
49 })
50 })
51 },
52 hook: { self.hook.unwrap_or_else(|| Arc::new(|_| {})) },
53 }
54 }
55
56 #[must_use]
57 pub fn take_call_from_existing(mut self) -> Self {
59 self.hook = Some(Arc::new(std::panic::take_hook()));
60 self
61 }
62
63 #[must_use]
64 pub fn set_call_func(mut self, call_func: impl PanicHandleFn<()>) -> Self {
66 self.hook = Some(Arc::new(call_func));
67 self
68 }
69
70 #[must_use]
71 pub fn set_title_func(mut self, title_func: impl PanicHandleFn<String>) -> Self {
73 self.name = Some(Arc::new(title_func));
74 self
75 }
76
77 #[must_use]
78 pub fn set_body_func(mut self, body_func: impl PanicHandleFn<String>) -> Self {
80 self.body = Some(Arc::new(body_func));
81 self
82 }
83}
84
85#[derive(Clone)]
87pub struct PanicHandler {
88 pub title: Arc<dyn PanicHandleFn<String>>,
89 pub body: Arc<dyn PanicHandleFn<String>>,
90 pub hook: Arc<dyn PanicHandleFn<()>>,
91}
92impl PanicHandler {
93 #[must_use]
94 #[allow(clippy::new_ret_no_self)]
95 pub fn new() -> PanicHandlerBuilder {
97 PanicHandlerBuilder::default()
98 }
99
100 #[must_use]
101 pub fn new_take_old() -> PanicHandlerBuilder {
103 PanicHandlerBuilder::default().take_call_from_existing()
104 }
105}
106
107impl Plugin for PanicHandler {
108 fn build(&self, _: &mut App) {
109 let handler = self.clone();
110 std::panic::set_hook(Box::new(move |info| {
111 #[cfg(not(test))]
112 let title_string = (handler.title)(info);
113 #[cfg(not(test))]
114 let info_string = (handler.body)(info);
115
116 #[cfg(all(not(test), feature = "log"))]
118 bevy::log::error!("{title_string}\n{info_string}");
119
120 #[cfg(all(
122 not(test),
123 any(target_os = "windows", target_os = "macos", target_family = "unix")
124 ))]
125 {
126 let builder = native_dialog::MessageDialogBuilder::default()
127 .set_title(&title_string)
128 .set_text(&info_string)
129 .set_level(native_dialog::MessageLevel::Error);
130 if let Err(e) = builder.alert().show() {
131 #[cfg(feature = "log")]
132 bevy::log::error!("{e}");
133 #[cfg(not(feature = "log"))]
134 {
135 _ = e;
136 }
137 }
138 }
139
140 (handler.hook)(info);
141 }));
142 }
143}