veloren_voxygen/
panic_handler.rs

1use std::{panic, panic::PanicHookInfo, path::PathBuf};
2use tracing::error;
3
4pub fn set_panic_hook(log_filename: String, logs_dir: PathBuf) {
5    // Set up panic handler to relay swish panic messages to the user
6    let default_hook = panic::take_hook();
7    panic::set_hook(Box::new(move |panic_info| {
8        let panic_info_payload = panic_info.payload();
9        let payload_string = panic_info_payload.downcast_ref::<String>();
10        let reason = match payload_string {
11            Some(s) => s,
12            None => {
13                let payload_str = panic_info_payload.downcast_ref::<&str>();
14                match payload_str {
15                    Some(st) => st,
16                    None => "Payload is not a string",
17                }
18            },
19        };
20        let potential_cause = potential_cause(panic_info);
21
22        let mut dialog_message = format!(
23            "A critical error has occurred and Voxygen has been forced to terminate in an unusual \
24             manner. Details about the error can be found below.\n\nPanic reason: {}\n\n",
25            reason
26        );
27
28        if let Some(potential_cause) = potential_cause {
29            // The error is a known error, so don't show the full bug report instructions
30            // and instead show a potential fix.
31            dialog_message.push_str(format!("Potential causes: {}\n\n", potential_cause).as_str())
32        } else {
33            dialog_message.push_str(
34                format!("> What should I do?\n\
35            \n\
36            We need your help to fix this! You can help by contacting us and \
37            reporting this problem. To do this, open an issue on the Veloren \
38            issue tracker:\n\
39            \n\
40            https://www.gitlab.com/veloren/veloren/issues/new\n\
41            \n\
42            If you're on the Veloren community Discord server, we'd be \
43            grateful if you could also post a message in the #support channel.
44            \n\
45            > What should I include?\n\
46            \n\
47            The error information below will be useful in finding and fixing \
48            the problem. Please include as much information about your setup \
49            and the events that led up to the panic as possible.
50            \n\
51            Voxygen has logged information about the problem (including this \
52            message) to the file {}. Please include the contents of this \
53            file in your bug report.
54            \n\n", logs_dir.join(&log_filename).display())
55                .as_str(),
56            );
57        }
58
59        dialog_message.push_str(
60            format!(
61                "> Error information\n\nThe information below is intended for developers and \
62                 testers.\n\nPanicHookInfo: {} \nGame version: {} [{}]",
63                panic_info,
64                *common::util::GIT_HASH,
65                *common::util::GIT_DATE
66            )
67            .as_str(),
68        );
69
70        error!(
71            "VOXYGEN HAS PANICKED\n\n{}\n\nBacktrace:\n{:?}",
72            dialog_message,
73            backtrace::Backtrace::new(),
74        );
75
76        #[cfg(feature = "native-dialog")]
77        {
78            use native_dialog::{MessageDialog, MessageType};
79
80            let mbox = move || {
81                MessageDialog::new()
82                    .set_title("Veloren has crashed!")
83                    //somehow `<` and `>` are invalid characters and cause the msg to get replaced
84                    // by some generic text thus i replace them
85                    .set_text(&dialog_message.replace('<', "[").replace('>', "]"))
86                    .set_type(MessageType::Error)
87                    .show_alert()
88                    .unwrap()
89            };
90
91            // On windows we need to spawn a thread as the msg doesn't work otherwise
92            #[cfg(target_os = "windows")]
93            {
94                let builder = std::thread::Builder::new().name("shutdown".into());
95                builder
96                    .spawn(move || {
97                        mbox();
98                    })
99                    .unwrap()
100                    .join()
101                    .unwrap();
102            }
103
104            #[cfg(not(target_os = "windows"))]
105            mbox();
106        }
107
108        default_hook(panic_info);
109    }));
110}
111
112enum PotentialPanicCause {
113    GraphicsCardIncompatibleWithRenderingBackend,
114}
115
116fn potential_cause(panic_info: &PanicHookInfo) -> Option<String> {
117    let location = panic_info
118        .location()
119        .map_or("".to_string(), |x| x.file().to_string())
120        .to_lowercase();
121
122    // Use some basic heuristics to determine the likely cause of the panic. This is
123    // deliberately overly simplistic as the vast majority of graphics errors
124    // for example are caused by an incompatible GPU rather than a bug.
125    let potential_cause = if location.contains("gfx") || location.contains("wgpu") {
126        Some(PotentialPanicCause::GraphicsCardIncompatibleWithRenderingBackend)
127    } else {
128        None
129    };
130
131    potential_cause.map(potential_cause_to_string)
132}
133
134fn potential_cause_to_string(potential_cause: PotentialPanicCause) -> String {
135    match potential_cause {
136        PotentialPanicCause::GraphicsCardIncompatibleWithRenderingBackend => {
137            "This error occurs when your graphics card is not compatible with the selected \
138             graphics mode. This can be changed in the Airshipper settings window, however it may \
139             be the case that your graphics card is not supported by any graphics mode."
140                .to_string()
141        },
142    }
143}