#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use std::sync::Arc;
use bevy::prelude::*;
pub trait PanicHandleFn<Res>:
Fn(&std::panic::PanicHookInfo) -> Res + Send + Sync + 'static
{
}
impl<Res, T: Fn(&std::panic::PanicHookInfo) -> Res + Send + Sync + 'static> PanicHandleFn<Res>
for T
{
}
#[derive(Default)]
pub struct PanicHandlerBuilder {
name: Option<Arc<dyn PanicHandleFn<String>>>,
body: Option<Arc<dyn PanicHandleFn<String>>>,
hook: Option<Arc<dyn PanicHandleFn<()>>>,
}
impl PanicHandlerBuilder {
#[must_use]
pub fn build(self) -> PanicHandler {
PanicHandler {
title: {
self.name.unwrap_or_else(|| {
Arc::new(|_: &std::panic::PanicHookInfo| "Fatal Error".to_owned())
})
},
body: {
self.body.unwrap_or_else(|| {
Arc::new(|info| {
format!(
"Unhandled panic! at {}:\n{}",
info.location()
.map_or_else(|| "Unknown Location".to_owned(), ToString::to_string),
info.payload().downcast_ref::<String>().map_or_else(
|| (*info.payload().downcast_ref::<&str>().unwrap_or(&"No Info"))
.to_string(),
ToOwned::to_owned,
)
)
})
})
},
hook: { self.hook.unwrap_or_else(|| Arc::new(|_| {})) },
}
}
#[must_use]
pub fn take_call_from_existing(mut self) -> Self {
self.hook = Some(Arc::new(std::panic::take_hook()));
self
}
#[must_use]
pub fn set_call_func(mut self, call_func: impl PanicHandleFn<()>) -> Self {
self.hook = Some(Arc::new(call_func));
self
}
#[must_use]
pub fn set_title_func(mut self, title_func: impl PanicHandleFn<String>) -> Self {
self.name = Some(Arc::new(title_func));
self
}
#[must_use]
pub fn set_body_func(mut self, body_func: impl PanicHandleFn<String>) -> Self {
self.body = Some(Arc::new(body_func));
self
}
}
#[derive(Clone)]
pub struct PanicHandler {
pub title: Arc<dyn PanicHandleFn<String>>,
pub body: Arc<dyn PanicHandleFn<String>>,
pub hook: Arc<dyn PanicHandleFn<()>>,
}
impl PanicHandler {
#[must_use]
#[allow(clippy::new_ret_no_self)]
pub fn new() -> PanicHandlerBuilder {
PanicHandlerBuilder::default()
}
#[must_use]
pub fn new_take_old() -> PanicHandlerBuilder {
PanicHandlerBuilder::default().take_call_from_existing()
}
}
impl Plugin for PanicHandler {
fn build(&self, _: &mut App) {
let handler = self.clone();
std::panic::set_hook(Box::new(move |info| {
#[cfg(not(test))]
let title_string = (handler.title)(info);
#[cfg(not(test))]
let info_string = (handler.body)(info);
#[cfg(all(not(test), feature = "log"))]
bevy::log::error!("{title_string}\n{info_string}");
#[cfg(all(
not(test),
any(target_os = "windows", target_os = "macos", target_family = "unix")
))]
{
let builder = native_dialog::MessageDialogBuilder::default()
.set_title(&title_string)
.set_text(&info_string)
.set_level(native_dialog::MessageLevel::Error);
if let Err(e) = builder.alert().show() {
#[cfg(feature = "log")]
bevy::log::error!("{e}");
#[cfg(not(feature = "log"))]
{
_ = e;
}
}
}
(handler.hook)(info);
}));
}
}