use crate::error::Error;
use std::any::type_name;
use std::panic::{AssertUnwindSafe, catch_unwind};
#[must_use]
pub fn run_audio_block<P>(format: &str, body: impl FnOnce()) -> bool {
let result = catch_unwind(AssertUnwindSafe(body));
if let Err(payload) = result {
eprintln!(
"[truce-rack {format}] panic in process() for plugin {}: {}",
type_name::<P>(),
extract_panic_msg(&payload),
);
return false;
}
true
}
pub fn run_audio_block_with<P, R>(format: &str, fallback: R, body: impl FnOnce() -> R) -> R {
match catch_unwind(AssertUnwindSafe(body)) {
Ok(value) => value,
Err(payload) => {
eprintln!(
"[truce-rack {format}] panic in process() for plugin {}: {}",
type_name::<P>(),
extract_panic_msg(&payload),
);
fallback
}
}
}
pub fn run_extern_callback_with<P, R>(
format: &str,
action: &str,
fallback: R,
body: impl FnOnce() -> R,
) -> R {
match catch_unwind(AssertUnwindSafe(body)) {
Ok(value) => value,
Err(payload) => {
eprintln!(
"[truce-rack {format}] panic in {action} for plugin {}: {}",
type_name::<P>(),
extract_panic_msg(&payload),
);
fallback
}
}
}
pub fn run_callable<P, T>(
action: &'static str,
body: impl FnOnce() -> crate::Result<T>,
) -> crate::Result<T> {
match catch_unwind(AssertUnwindSafe(body)) {
Ok(result) => result,
Err(payload) => Err(Error::Panic {
action,
message: extract_panic_msg(&payload).to_string(),
}),
}
}
fn extract_panic_msg(payload: &Box<dyn std::any::Any + Send>) -> &str {
if let Some(s) = payload.downcast_ref::<&'static str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s.as_str()
} else {
"<non-string panic payload>"
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Dummy;
#[test]
fn clean_audio_block_returns_true() {
let ran = std::cell::Cell::new(false);
let result = run_audio_block::<Dummy>("test", || ran.set(true));
assert!(result);
assert!(ran.get());
}
#[test]
fn panicking_audio_block_returns_false() {
let result = run_audio_block::<Dummy>("test", || panic!("boom"));
assert!(!result);
}
#[test]
fn callable_panic_becomes_error() {
let result: crate::Result<()> = run_callable::<Dummy, ()>("save_state", || panic!("oops"));
match result {
Err(Error::Panic { action, .. }) => assert_eq!(action, "save_state"),
_ => panic!("expected Error::Panic"),
}
}
#[test]
fn callable_error_propagates() {
let result: crate::Result<()> =
run_callable::<Dummy, ()>("save_state", || Err(Error::Other("plain error".into())));
assert!(matches!(result, Err(Error::Other(_))));
}
}