use std::{
cell::RefCell,
ffi::{CStr, c_char, c_void},
ptr,
sync::Once,
};
use thiserror::Error;
use crate::raw;
static ERROR_HANDLER_INSTALLATION: Once = Once::new();
thread_local! {
static LAST_MLX_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
}
thread_local! {
static LAST_CLOSURE_ERROR: RefCell<Option<MlxCError>> = const { RefCell::new(None) };
}
#[derive(Debug, Error)]
#[error("MLX operation {operation} failed: {description}")]
pub struct MlxCError {
pub operation: &'static str,
pub description: String,
}
pub fn install_non_terminating_error_handler() {
ERROR_HANDLER_INSTALLATION.call_once(|| {
unsafe {
raw::mlx_set_error_handler(Some(capture_mlx_error), ptr::null_mut(), None);
}
});
}
pub fn check_status(status: i32, operation: &'static str) -> Result<(), MlxCError> {
if status == 0 {
clear_captured_mlx_error();
clear_closure_error();
return Ok(());
}
if let Some(failure) = take_closure_error() {
clear_captured_mlx_error();
return Err(failure);
}
let description = take_captured_mlx_error()
.unwrap_or_else(|| format!("MLX C returned status {status} without an error message"));
Err(MlxCError {
operation,
description,
})
}
#[must_use]
pub fn take_captured_mlx_error() -> Option<String> {
LAST_MLX_ERROR.with(|last_error| {
last_error
.try_borrow_mut()
.ok()
.and_then(|mut writable_error| writable_error.take())
})
}
pub fn clear_captured_mlx_error() {
LAST_MLX_ERROR.with(|last_error| {
if let Ok(mut writable_error) = last_error.try_borrow_mut() {
*writable_error = None;
}
});
}
pub fn set_closure_error(failure: MlxCError) {
LAST_CLOSURE_ERROR.with(|last_failure| {
if let Ok(mut writable_failure) = last_failure.try_borrow_mut() {
*writable_failure = Some(failure);
}
});
}
#[must_use]
pub fn take_closure_error() -> Option<MlxCError> {
LAST_CLOSURE_ERROR.with(|last_failure| {
last_failure
.try_borrow_mut()
.ok()
.and_then(|mut writable_failure| writable_failure.take())
})
}
pub fn clear_closure_error() {
LAST_CLOSURE_ERROR.with(|last_failure| {
if let Ok(mut writable_failure) = last_failure.try_borrow_mut() {
*writable_failure = None;
}
});
}
unsafe extern "C" fn capture_mlx_error(message: *const c_char, _context: *mut c_void) {
let description = if message.is_null() {
"MLX reported an error without a message".to_owned()
} else {
unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned()
};
LAST_MLX_ERROR.with(|last_error| {
if let Ok(mut writable_error) = last_error.try_borrow_mut() {
*writable_error = Some(description);
}
});
}