use std::{any::TypeId, cell::Cell, ptr::NonNull};
use crate::{Lisp, LispError, Value, sys};
#[repr(C)]
struct ScopedPointerCell {
type_id: TypeId,
pointer: Option<NonNull<()>>,
borrowed: Cell<bool>,
}
pub unsafe fn with_scoped_object_pointer<T: 'static, R>(
handle: sys::cl_object,
callback: impl FnOnce(&mut T) -> R,
) -> Option<R> {
let data = unsafe {
sys::eclisp_scoped_pointer_data(handle, size_of::<ScopedPointerCell>())
.cast::<ScopedPointerCell>()
};
let cell = NonNull::new(data)?;
let cell_ref = unsafe { cell.as_ref() };
if cell_ref.type_id != TypeId::of::<T>() {
return None;
}
let pointer = cell_ref.pointer?.cast::<T>();
if cell_ref.borrowed.replace(true) {
return None;
}
struct BorrowGuard<'a>(&'a Cell<bool>);
impl Drop for BorrowGuard<'_> {
fn drop(&mut self) {
self.0.set(false);
}
}
let _guard = BorrowGuard(&cell_ref.borrowed);
Some(callback(unsafe { &mut *pointer.as_ptr() }))
}
pub(crate) unsafe extern "C" fn scoped_pointer_valid<T: 'static>(
handle: sys::cl_object,
) -> sys::cl_object {
if unsafe { with_scoped_object_pointer::<T, _>(handle, |_| ()) }.is_some() {
unsafe { sys::eclisp_t_raw() }
} else {
unsafe { sys::eclisp_nil_raw() }
}
}
pub(crate) unsafe extern "C" fn native_failure_p(object: sys::cl_object) -> sys::cl_object {
if unsafe { sys::eclisp_is_native_failure(object) } != 0 {
unsafe { sys::eclisp_t_raw() }
} else {
unsafe { sys::eclisp_nil_raw() }
}
}
pub(crate) struct ScopedObject<'rt> {
handle: Value<'rt>,
cell: NonNull<ScopedPointerCell>,
}
impl<'rt> ScopedObject<'rt> {
pub(crate) fn new<T: 'static>(runtime: &'rt Lisp, value: &mut T) -> Result<Self, LispError> {
let mut root = std::ptr::null_mut();
let status =
unsafe { sys::eclisp_scoped_pointer_new(size_of::<ScopedPointerCell>(), &mut root) };
if status != sys::ECLISP_OK {
return Err(LispError::RuntimeError(format!(
"Failed to create scoped pointer: {status}"
)));
}
let root = NonNull::new(root)
.ok_or_else(|| LispError::RuntimeError("Missing Lisp root".into()))?;
let handle = unsafe { Value::from_root(runtime, root) };
let data = unsafe {
sys::eclisp_scoped_pointer_data(handle.as_raw(), size_of::<ScopedPointerCell>())
.cast::<ScopedPointerCell>()
};
let cell = NonNull::new(data)
.ok_or_else(|| LispError::RuntimeError("Missing scoped pointer data".into()))?;
unsafe {
cell.as_ptr().write(ScopedPointerCell {
type_id: TypeId::of::<T>(),
pointer: NonNull::new(value).map(NonNull::cast),
borrowed: Cell::new(false),
});
}
Ok(Self { handle, cell })
}
pub(crate) fn handle(&self) -> &Value<'rt> {
&self.handle
}
}
impl Drop for ScopedObject<'_> {
fn drop(&mut self) {
unsafe {
self.cell.as_mut().pointer = None;
}
}
}