#![no_std]
extern crate alloc;
use alloc::{
boxed::Box,
string::{String, ToString},
};
use core::{
fmt::Write,
sync::atomic::{AtomicBool, Ordering},
};
use vexide_core::{backtrace::Backtrace, println, sync::Mutex};
#[cfg(feature = "display_panics")]
use vexide_devices::{
display::{Display, Font, FontFamily, FontSize, Rect, Text},
math::Point2,
};
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
#[cfg(feature = "display_panics")]
fn draw_error(display: &mut Display, msg: &str, backtrace: &Backtrace) {
const ERROR_BOX_MARGIN: i16 = 16;
const ERROR_BOX_PADDING: i16 = 16;
const LINE_HEIGHT: i16 = 20;
const LINE_MAX_WIDTH: usize = 52;
fn draw_text(screen: &mut Display, buffer: &str, line: i16) {
screen.draw_text(
&Text::new(
buffer,
Font::new(FontSize::SMALL, FontFamily::Monospace),
Point2 {
x: ERROR_BOX_MARGIN + ERROR_BOX_PADDING,
y: ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * LINE_HEIGHT),
},
),
(255, 255, 255),
None,
);
}
display.set_render_mode(vexide_devices::display::RenderMode::Immediate);
let error_box_rect = Rect::new(
Point2 {
x: ERROR_BOX_MARGIN,
y: ERROR_BOX_MARGIN,
},
Point2 {
x: Display::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN,
y: Display::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN,
},
);
display.fill(&error_box_rect, (255, 0, 0));
display.stroke(&error_box_rect, (255, 255, 255));
let mut buffer = String::new();
let mut line: i16 = 0;
for (i, character) in msg.char_indices() {
if !character.is_ascii_control() {
buffer.push(character);
}
if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) {
draw_text(display, &buffer, line);
line += 1;
buffer.clear();
}
}
if !buffer.is_empty() {
draw_text(display, &buffer, line);
line += 1;
}
line += 1;
draw_text(display, "stack backtrace:", line);
line += 1;
if !backtrace.frames.is_empty() {
const ROW_LENGTH: usize = 3;
for (col, frames) in backtrace.frames.chunks(ROW_LENGTH).enumerate() {
let mut msg = String::new();
for (row, frame) in frames.iter().enumerate() {
write!(msg, "{:>3}: {:?} ", col * ROW_LENGTH + row, frame).unwrap();
}
draw_text(display, msg.trim_end(), line);
line += 1;
}
}
}
pub fn default_panic_hook(info: &core::panic::PanicInfo<'_>) {
println!("{info}");
let backtrace = Backtrace::capture();
#[cfg(feature = "display_panics")]
draw_error(
&mut unsafe { Display::new() },
&info.to_string(),
&backtrace,
);
if !backtrace.frames.is_empty() {
println!("{backtrace}");
}
#[cfg(not(feature = "display_panics"))]
vexide_core::program::exit();
}
enum Hook {
Default,
Custom(Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send>),
}
impl Hook {
#[inline]
fn into_box(self) -> Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send> {
match self {
Hook::Default => Box::new(default_panic_hook),
Hook::Custom(hook) => hook,
}
}
}
static HOOK: Mutex<Hook> = Mutex::new(Hook::Default);
pub fn set_hook<F>(hook: F)
where
F: Fn(&core::panic::PanicInfo<'_>) + Send + 'static,
{
let mut guard = HOOK
.try_lock()
.expect("failed to set custom panic hook (mutex poisoned or locked)");
let old_handler = core::mem::replace(&mut *guard, Hook::Custom(Box::new(hook))).into_box();
core::mem::drop(guard);
core::mem::drop(old_handler); }
pub fn take_hook() -> Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send> {
let mut guard = HOOK
.try_lock()
.expect("failed to set custom panic hook (mutex locked)");
let old_hook = core::mem::replace(&mut *guard, Hook::Default).into_box();
core::mem::drop(guard);
old_hook
}
#[panic_handler]
pub fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
if !FIRST_PANIC.swap(false, Ordering::Relaxed) {
vexide_core::program::exit();
}
#[allow(if_let_rescope)]
if let Some(mut guard) = HOOK.try_lock() {
let hook = core::mem::replace(&mut *guard, Hook::Default);
core::mem::drop(guard);
(hook.into_box())(info);
} else {
println!("Panic handler hook mutex was locked, so the default panic hook will be used. This should never happen.");
println!("If you see this, please consider filing a bug: https://github.com/vexide/vexide/issues/new");
default_panic_hook(info);
}
loop {
unsafe {
vex_sdk::vexTasksRun();
}
}
}