use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::ptr;
use super::{Binary, Decoder, Encoder, Env, Error, NifResult, Term};
use crate::wrapper::{
c_void, resource, NifResourceFlags, MUTABLE_NIF_RESOURCE_HANDLE, NIF_ENV, NIF_RESOURCE_TYPE,
};
#[doc(hidden)]
pub use crate::wrapper::NIF_RESOURCE_FLAGS;
#[doc(hidden)]
pub struct ResourceType<T> {
pub res: NIF_RESOURCE_TYPE,
pub struct_type: PhantomData<T>,
}
#[doc(hidden)]
pub trait ResourceTypeProvider: Sized + Send + Sync + 'static {
fn get_type() -> &'static ResourceType<Self>;
}
impl<T> Encoder for ResourceArc<T>
where
T: ResourceTypeProvider,
{
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
self.as_term(env)
}
}
impl<'a, T> Decoder<'a> for ResourceArc<T>
where
T: ResourceTypeProvider + 'a,
{
fn decode(term: Term<'a>) -> NifResult<Self> {
ResourceArc::from_term(term)
}
}
extern "C" fn resource_destructor<T>(_env: NIF_ENV, handle: MUTABLE_NIF_RESOURCE_HANDLE) {
unsafe {
let aligned = align_alloced_mem_for_struct::<T>(handle);
let res = aligned as *mut T;
ptr::read(res);
}
}
#[doc(hidden)]
pub fn open_struct_resource_type<T: ResourceTypeProvider>(
env: Env,
name: &str,
flags: NifResourceFlags,
) -> Option<ResourceType<T>> {
let res: Option<NIF_RESOURCE_TYPE> = unsafe {
resource::open_resource_type(
env.as_c_arg(),
name.as_bytes(),
Some(resource_destructor::<T>),
flags,
)
};
res.map(|r| ResourceType {
res: r,
struct_type: PhantomData,
})
}
fn get_alloc_size_struct<T>() -> usize {
mem::size_of::<T>() + mem::align_of::<T>()
}
unsafe fn align_alloced_mem_for_struct<T>(ptr: *const c_void) -> *const c_void {
let offset = mem::align_of::<T>() - ((ptr as usize) % mem::align_of::<T>());
ptr.add(offset)
}
pub struct ResourceArc<T>
where
T: ResourceTypeProvider,
{
raw: *const c_void,
inner: *mut T,
}
unsafe impl<T> Send for ResourceArc<T> where T: ResourceTypeProvider {}
unsafe impl<T> Sync for ResourceArc<T> where T: ResourceTypeProvider {}
impl<T> ResourceArc<T>
where
T: ResourceTypeProvider,
{
pub fn new(data: T) -> Self {
let alloc_size = get_alloc_size_struct::<T>();
let mem_raw = unsafe { resource::alloc_resource(T::get_type().res, alloc_size) };
let aligned_mem = unsafe { align_alloced_mem_for_struct::<T>(mem_raw) as *mut T };
unsafe { ptr::write(aligned_mem, data) };
ResourceArc {
raw: mem_raw,
inner: aligned_mem,
}
}
pub fn make_binary<'env, 'a, F>(&self, env: Env<'env>, f: F) -> Binary<'env>
where
F: FnOnce(&'a T) -> &'a [u8],
{
unsafe { self.make_binary_unsafe(env, f) }
}
pub unsafe fn make_binary_unsafe<'env, 'a, 'b, F>(&self, env: Env<'env>, f: F) -> Binary<'env>
where
F: FnOnce(&'a T) -> &'b [u8],
{
let bin = f(&*self.inner);
let binary = rustler_sys::enif_make_resource_binary(
env.as_c_arg(),
self.raw,
bin.as_ptr() as *const c_void,
bin.len(),
);
let term = Term::new(env, binary);
Binary::from_term_and_slice(term, bin)
}
fn from_term(term: Term) -> Result<Self, Error> {
let res_resource = match unsafe {
resource::get_resource(
term.get_env().as_c_arg(),
term.as_c_arg(),
T::get_type().res,
)
} {
Some(res) => res,
None => return Err(Error::BadArg),
};
unsafe {
resource::keep_resource(res_resource);
}
let casted_ptr = unsafe { align_alloced_mem_for_struct::<T>(res_resource) as *mut T };
Ok(ResourceArc {
raw: res_resource,
inner: casted_ptr,
})
}
fn as_term<'a>(&self, env: Env<'a>) -> Term<'a> {
unsafe { Term::new(env, resource::make_resource(env.as_c_arg(), self.raw)) }
}
fn as_c_arg(&mut self) -> *const c_void {
self.raw
}
fn inner(&self) -> &T {
unsafe { &*self.inner }
}
}
impl<T> Deref for ResourceArc<T>
where
T: ResourceTypeProvider,
{
type Target = T;
fn deref(&self) -> &T {
self.inner()
}
}
impl<T> Clone for ResourceArc<T>
where
T: ResourceTypeProvider,
{
fn clone(&self) -> Self {
unsafe {
resource::keep_resource(self.raw);
}
ResourceArc {
raw: self.raw,
inner: self.inner,
}
}
}
impl<T> Drop for ResourceArc<T>
where
T: ResourceTypeProvider,
{
fn drop(&mut self) {
unsafe { rustler_sys::enif_release_resource(self.as_c_arg()) };
}
}
#[macro_export]
macro_rules! resource {
($struct_name:ty, $env: ident) => {
{
static mut STRUCT_TYPE: Option<$crate::resource::ResourceType<$struct_name>> = None;
let temp_struct_type =
match $crate::resource::open_struct_resource_type::<$struct_name>(
$env,
concat!(stringify!($struct_name), "\x00"),
$crate::resource::NIF_RESOURCE_FLAGS::ERL_NIF_RT_CREATE
) {
Some(inner) => inner,
None => {
println!("Failure in creating resource type");
return false;
}
};
unsafe { STRUCT_TYPE = Some(temp_struct_type) };
impl $crate::resource::ResourceTypeProvider for $struct_name {
fn get_type() -> &'static $crate::resource::ResourceType<Self> {
unsafe { &STRUCT_TYPE }.as_ref()
.expect("The resource type hasn't been initialized. Did you remember to call the function where you used the `resource!` macro?")
}
}
}
}
}