use std::{
ffi::CString,
os::raw::{c_int, c_void},
};
use savvy_ffi::{
R_NilValue, R_xlen_t, RAW, RAW_ELT, RAWSXP, Rboolean, Rboolean_FALSE, Rboolean_TRUE,
Rf_coerceVector, Rf_duplicate, Rf_protect, Rf_unprotect, Rf_xlength, SEXP, SEXPTYPE,
altrep::{
R_altrep_data2, R_make_altraw_class, R_set_altraw_Elt_method, R_set_altrep_Coerce_method,
R_set_altrep_Duplicate_method, R_set_altrep_Inspect_method, R_set_altrep_Length_method,
R_set_altrep_data2, R_set_altvec_Dataptr_method, R_set_altvec_Dataptr_or_null_method,
},
};
use crate::{IntoExtPtrSexp, RawSexp};
pub trait AltRaw: Sized + IntoExtPtrSexp {
const CLASS_NAME: &'static str;
const PACKAGE_NAME: &'static str;
fn length(&mut self) -> usize;
fn elt(&mut self, i: usize) -> u8;
fn copy_to(&mut self, new: &mut [u8], offset: usize) {
if offset + new.len() > self.length() {
return;
}
for (i, v) in new.iter_mut().enumerate() {
*v = self.elt(i + offset) as _;
}
}
fn inspect(&mut self, is_materialized: bool) {
crate::io::r_print(
&format!("{} (materialized: {is_materialized})\n", Self::CLASS_NAME),
false,
);
}
fn into_altrep(self) -> crate::Result<crate::Sexp> {
super::create_altrep_instance(self, Self::CLASS_NAME).map(crate::Sexp)
}
fn try_from_altrep_ref(x: &RawSexp) -> crate::Result<&Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
super::extract_ref_from_altrep(&x.0)
}
fn try_from_altrep_mut(x: &mut RawSexp, invalidate_cache: bool) -> crate::Result<&mut Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
if invalidate_cache {
unsafe { R_set_altrep_data2(x.0, R_NilValue) }
}
super::extract_mut_from_altrep(&mut x.0)
}
fn try_from_altrep(x: RawSexp) -> crate::Result<Self> {
super::assert_altrep_class(x.0, Self::CLASS_NAME)?;
super::extract_from_altrep(x.0)
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn register_altraw_class<T: AltRaw>(
dll_info: *mut crate::ffi::DllInfo,
) -> crate::error::Result<()> {
let class_name = CString::new(T::CLASS_NAME).unwrap_or_default();
let package_name = CString::new(T::PACKAGE_NAME).unwrap_or_default();
let class_t =
unsafe { R_make_altraw_class(class_name.as_ptr(), package_name.as_ptr(), dll_info) };
#[allow(clippy::mut_from_ref)]
#[inline]
fn get_materialized_sexp<T: AltRaw>(x: &mut SEXP, allow_allocate: bool) -> Option<SEXP> {
let data = unsafe { R_altrep_data2(*x) };
if unsafe { data != R_NilValue } {
return Some(data);
}
if !allow_allocate {
return None;
}
let self_: &mut T = match super::extract_mut_from_altrep(x) {
Ok(self_) => self_,
Err(_) => return None,
};
let len = self_.length();
let new = crate::alloc_vector(RAWSXP, len).unwrap();
unsafe { Rf_protect(new) };
self_.copy_to(unsafe { std::slice::from_raw_parts_mut(RAW(new), len) }, 0);
crate::log::debug!("A {} object is materialized", T::CLASS_NAME);
unsafe { R_set_altrep_data2(*x, new) };
unsafe { Rf_unprotect(1) };
Some(new)
}
unsafe extern "C" fn altrep_duplicate<T: AltRaw>(mut x: SEXP, _deep_copy: Rboolean) -> SEXP {
crate::log::trace!("A {} object is duplicated", T::CLASS_NAME);
let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
unsafe { Rf_duplicate(materialized) }
}
unsafe extern "C" fn altrep_coerce<T: AltRaw>(mut x: SEXP, sexp_type: SEXPTYPE) -> SEXP {
crate::log::trace!("A {} object is coerced", T::CLASS_NAME);
let materialized = get_materialized_sexp::<T>(&mut x, true).expect("Must have result");
unsafe { Rf_coerceVector(materialized, sexp_type) }
}
fn altvec_dataptr_inner<T: AltRaw>(mut x: SEXP, allow_allocate: bool) -> *mut c_void {
match get_materialized_sexp::<T>(&mut x, allow_allocate) {
Some(materialized) => unsafe { RAW(materialized) as _ },
None => std::ptr::null_mut(),
}
}
unsafe extern "C" fn altvec_dataptr<T: AltRaw>(x: SEXP, _writable: Rboolean) -> *mut c_void {
crate::log::trace!("DATAPTR({}) is called", T::CLASS_NAME);
altvec_dataptr_inner::<T>(x, true)
}
unsafe extern "C" fn altvec_dataptr_or_null<T: AltRaw>(x: SEXP) -> *const c_void {
crate::log::trace!("DATAPTR_OR_NULL({}) is called", T::CLASS_NAME);
altvec_dataptr_inner::<T>(x, false)
}
unsafe extern "C" fn altrep_length<T: AltRaw>(mut x: SEXP) -> R_xlen_t {
if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
unsafe { Rf_xlength(materialized) }
} else {
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => self_.length() as _,
Err(_) => 0,
}
}
}
unsafe extern "C" fn altrep_inspect<T: AltRaw>(
mut x: SEXP,
_: c_int,
_: c_int,
_: c_int,
_: Option<unsafe extern "C" fn(SEXP, c_int, c_int, c_int)>,
) -> Rboolean {
let is_materialized = unsafe { R_altrep_data2(x) != R_NilValue };
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => {
self_.inspect(is_materialized);
Rboolean_TRUE
}
Err(_) => Rboolean_FALSE,
}
}
unsafe extern "C" fn altraw_elt<T: AltRaw>(mut x: SEXP, i: R_xlen_t) -> u8 {
crate::log::trace!("RAW_ELT({}, {i}) is called", T::CLASS_NAME);
if let Some(materialized) = get_materialized_sexp::<T>(&mut x, false) {
unsafe { RAW_ELT(materialized, i) }
} else {
match super::extract_mut_from_altrep::<T>(&mut x) {
Ok(self_) => self_.elt(i as _) as _,
Err(_) => 0, }
}
}
unsafe {
R_set_altrep_Length_method(class_t, Some(altrep_length::<T>));
R_set_altrep_Inspect_method(class_t, Some(altrep_inspect::<T>));
R_set_altrep_Duplicate_method(class_t, Some(altrep_duplicate::<T>));
R_set_altrep_Coerce_method(class_t, Some(altrep_coerce::<T>));
R_set_altvec_Dataptr_method(class_t, Some(altvec_dataptr::<T>));
R_set_altvec_Dataptr_or_null_method(class_t, Some(altvec_dataptr_or_null::<T>));
R_set_altraw_Elt_method(class_t, Some(altraw_elt::<T>));
}
super::register_altrep_class(T::CLASS_NAME, class_t)?;
Ok(())
}