use std::borrow::Cow;
use std::ffi::{c_char, CStr};
use std::io::Write;
use std::panic::{catch_unwind, AssertUnwindSafe};
use oxijolt_sys::JPH_SetAssertFailureHandler;
const UPDATE_ERROR_EXPRESSION: &str = "errors == EPhysicsUpdateError::None";
const UPDATE_ERROR_MESSAGE: &str =
"An error occurred during the physics update, see EPhysicsUpdateError for more information";
const UPDATE_ERROR_FILE: &str = "Jolt/Physics/PhysicsSystem.cpp";
const UPDATE_ERROR_LINE: u32 = 679;
fn continues_after(expression: &str, message: Option<&str>, file: &str, line: u32) -> bool {
expression == UPDATE_ERROR_EXPRESSION
&& message == Some(UPDATE_ERROR_MESSAGE)
&& is_update_error_file(file)
&& line == UPDATE_ERROR_LINE
}
fn is_update_error_file(file: &str) -> bool {
let file = file.replace('\\', "/");
file == UPDATE_ERROR_FILE
|| file
.strip_suffix(UPDATE_ERROR_FILE)
.is_some_and(|prefix| prefix.ends_with('/'))
}
unsafe fn jolt_text<'a>(text: *const c_char) -> Option<Cow<'a, str>> {
if text.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(text) }.to_string_lossy())
}
unsafe extern "C" fn on_assert_failed(
expression: *const c_char,
message: *const c_char,
file: *const c_char,
line: u32,
) -> bool {
let handled = catch_unwind(AssertUnwindSafe(|| {
let (expression, message, file) =
unsafe { (jolt_text(expression), jolt_text(message), jolt_text(file)) };
let expression = expression.unwrap_or_default();
let file = file.unwrap_or_default();
let mut stderr = std::io::stderr().lock();
if continues_after(&expression, message.as_deref(), &file, line) {
let _ = writeln!(
stderr,
"Jolt reported a physics update error (see StepReport): {file}:{line}"
);
return;
}
let _ = writeln!(
stderr,
"Jolt assertion failed: {file}:{line}: ({expression}) {}",
message.unwrap_or_default()
);
std::process::abort();
}));
if handled.is_err() {
std::process::abort();
}
false
}
pub(crate) fn install() {
unsafe { JPH_SetAssertFailureHandler(Some(on_assert_failed)) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_the_pinned_update_error_continues() {
let windows =
r"D:\a\jolt\crates\oxijolt-sys\vendor\JoltPhysics\Jolt\Physics\PhysicsSystem.cpp";
let unix = "/home/runner/vendor/JoltPhysics/Jolt/Physics/PhysicsSystem.cpp";
let message = Some(UPDATE_ERROR_MESSAGE);
for file in [windows, unix, UPDATE_ERROR_FILE] {
assert!(continues_after(
UPDATE_ERROR_EXPRESSION,
message,
file,
UPDATE_ERROR_LINE
));
}
let cases: [(&str, Option<&str>, &str, u32); 9] = [
("isfinite(len_sq)", message, unix, UPDATE_ERROR_LINE),
(UPDATE_ERROR_EXPRESSION, None, unix, UPDATE_ERROR_LINE),
(
UPDATE_ERROR_EXPRESSION,
Some("other"),
unix,
UPDATE_ERROR_LINE,
),
(
UPDATE_ERROR_EXPRESSION,
message,
"/x/Jolt/Physics/Body/MotionProperties.inl",
UPDATE_ERROR_LINE,
),
(
UPDATE_ERROR_EXPRESSION,
message,
"/x/Jolt/Physics/XPhysicsSystem.cpp",
UPDATE_ERROR_LINE,
),
(
UPDATE_ERROR_EXPRESSION,
message,
"/tmp/NotJolt/Physics/PhysicsSystem.cpp",
UPDATE_ERROR_LINE,
),
(
UPDATE_ERROR_EXPRESSION,
message,
r"D:\a\NotJolt\Physics\PhysicsSystem.cpp",
UPDATE_ERROR_LINE,
),
(UPDATE_ERROR_EXPRESSION, message, unix, 678),
(UPDATE_ERROR_EXPRESSION, message, unix, 680),
];
for (expression, message, file, line) in cases {
assert!(
!continues_after(expression, message, file, line),
"{expression} {message:?} {file} {line}"
);
}
}
}