use savvy_ffi::{
R_ClearExternalPtr, R_ExternalPtrAddr, R_MakeExternalPtr, R_NilValue, R_RegisterCFinalizerEx,
SEXP,
};
use crate::{protect::local_protect, Sexp};
pub trait IntoExtPtrSexp: Sized {
fn into_external_pointer(self) -> Sexp {
let boxed = Box::new(self);
let ptr = Box::into_raw(boxed);
unsafe extern "C" fn finalizer<T>(x: SEXP) {
let ptr = unsafe { R_ExternalPtrAddr(x) };
if !ptr.is_null() {
let rust_obj = unsafe { Box::from_raw(ptr as *mut T) };
drop(rust_obj);
}
unsafe { R_ClearExternalPtr(x) };
}
unsafe {
let external_pointer =
R_MakeExternalPtr(ptr as *mut std::os::raw::c_void, R_NilValue, R_NilValue);
let _external_pointer_guard = local_protect(external_pointer);
R_RegisterCFinalizerEx(external_pointer, Some(finalizer::<Self>), 1);
Sexp(external_pointer)
}
}
}
pub unsafe fn get_external_pointer_addr(
x: SEXP,
) -> crate::error::Result<*mut std::os::raw::c_void> {
let ptr = unsafe { R_ExternalPtrAddr(x) };
if ptr.is_null() {
return Err(crate::error::Error::InvalidPointer);
}
Ok(ptr)
}
pub unsafe fn take_external_pointer_value<T>(x: SEXP) -> crate::error::Result<T> {
let ptr = unsafe { R_ExternalPtrAddr(x) };
if !ptr.is_null() {
let rust_obj = unsafe { Box::from_raw(ptr as *mut T) };
unsafe { R_ClearExternalPtr(x) };
Ok(*rust_obj)
} else {
Err(crate::error::Error::InvalidPointer)
}
}
pub struct ExternalPointerSexp(pub SEXP);
impl ExternalPointerSexp {
pub fn inner(&self) -> SEXP {
self.0
}
pub unsafe fn cast_unchecked<T>(&self) -> *const T {
unsafe { savvy_ffi::R_ExternalPtrAddr(self.0) as _ }
}
pub unsafe fn cast_mut_unchecked<T>(&self) -> *mut T {
unsafe { savvy_ffi::R_ExternalPtrAddr(self.0) as _ }
}
}
impl TryFrom<Sexp> for ExternalPointerSexp {
type Error = crate::error::Error;
fn try_from(value: Sexp) -> crate::error::Result<Self> {
value.assert_external_pointer()?;
Ok(Self(value.0))
}
}