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 custom_name: Option<Arc<dyn PanicHandleFn<String>>>,
23 custom_body: Option<Arc<dyn PanicHandleFn<String>>>,
24 custom_hook: Option<Arc<dyn PanicHandleFn<()>>>,
25}
26impl PanicHandlerBuilder {
27 #[must_use]
28 pub fn build(self) -> PanicHandler {
30 PanicHandler {
31 custom_title: {
32 self.custom_name.unwrap_or_else(|| {
33 Arc::new(|_: &std::panic::PanicHookInfo| "Fatal Error".to_owned())
34 })
35 },
36 custom_body: {
37 self.custom_body.unwrap_or_else(|| {
38 Arc::new(|info| {
39 format!(
40 "Unhandled panic! @ {}:\n{}",
41 info.location()
42 .map_or("Unknown Location".to_owned(), ToString::to_string),
43 info.payload().downcast_ref::<String>().unwrap_or(
44 &((*info.payload().downcast_ref::<&str>().unwrap_or(&"No Info"))
45 .to_string())
46 )
47 )
48 })
49 })
50 },
51 custom_hook: { self.custom_hook.unwrap_or_else(|| Arc::new(|_| {})) },
52 }
53 }
54
55 #[must_use]
56 pub fn take_call_from_existing(mut self) -> Self {
58 self.custom_hook = Some(Arc::new(std::panic::take_hook()));
59 self
60 }
61
62 #[must_use]
63 pub fn set_call_func(mut self, call_func: impl PanicHandleFn<()>) -> Self {
65 self.custom_hook = Some(Arc::new(call_func));
66 self
67 }
68
69 #[must_use]
70 pub fn set_title_func(mut self, title_func: impl PanicHandleFn<String>) -> Self {
72 self.custom_name = Some(Arc::new(title_func));
73 self
74 }
75
76 #[must_use]
77 pub fn set_body_func(mut self, body_func: impl PanicHandleFn<String>) -> Self {
79 self.custom_body = Some(Arc::new(body_func));
80 self
81 }
82}
83
84#[derive(Clone)]
86pub struct PanicHandler {
87 pub custom_title: Arc<dyn PanicHandleFn<String>>,
88 pub custom_body: Arc<dyn PanicHandleFn<String>>,
89 pub custom_hook: Arc<dyn PanicHandleFn<()>>,
90}
91impl PanicHandler {
92 #[must_use]
93 #[allow(clippy::new_ret_no_self)]
94 pub fn new() -> PanicHandlerBuilder {
96 PanicHandlerBuilder::default()
97 }
98
99 #[must_use]
100 pub fn new_take_old() -> PanicHandlerBuilder {
102 PanicHandlerBuilder::default().take_call_from_existing()
103 }
104}
105
106impl Plugin for PanicHandler {
107 fn build(&self, _: &mut App) {
108 let handler = self.clone();
109 std::panic::set_hook(Box::new(move |info| {
110 let title_string = (handler.custom_title)(info);
111 let info_string = (handler.custom_body)(info);
112
113 bevy::log::error!("{title_string}\n{info_string}");
116
117 #[cfg(all(
119 not(test),
120 any(target_os = "windows", target_os = "macos", target_os = "linux")
121 ))]
122 {
123 if let Err(e) = native_dialog::MessageDialog::new()
124 .set_title(&title_string)
125 .set_text(&info_string)
126 .set_type(native_dialog::MessageType::Error)
127 .show_alert()
128 {
129 bevy::log::error!("{e}");
130 }
131 }
132
133 (handler.custom_hook)(info);
134 }));
135 }
136}