use std::ffi::{c_int, c_void};
use std::marker::PhantomData;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::codec::{CodecError, Decoder, Encoder};
use crate::priv_data::{PrivData, ResourceRegistry};
use crate::types::{AnyTerm, CallbackEnv, Env, InitEnv, LocalPid, Term};
pub struct ResourceTypeHandle<T: Resource> {
ptr: *mut enif_ffi::ResourceType,
_t: PhantomData<fn() -> T>,
}
unsafe impl<T: Resource> Send for ResourceTypeHandle<T> {}
unsafe impl<T: Resource> Sync for ResourceTypeHandle<T> {}
impl<T: Resource> ResourceTypeHandle<T> {
pub fn make(self, val: T) -> ResourceArc<T> {
let alloc_size = std::mem::size_of::<T>() + std::mem::align_of::<T>() - 1;
let raw = unsafe { enif_ffi::alloc_resource(self.ptr, alloc_size) };
assert!(!raw.is_null(), "enif_alloc_resource returned null");
let inner = align_ptr::<T>(raw);
unsafe { std::ptr::write(inner, val) };
ResourceArc { raw, inner }
}
}
fn registry<'id>(env: impl Env<'id>) -> Option<&'id ResourceRegistry> {
let pd = unsafe { enif_ffi::priv_data(env.raw_env()) } as *const PrivData;
if pd.is_null() {
return None;
}
Some(unsafe { (*pd).registry() })
}
pub fn resource_handle<'id, T: Resource>(env: impl Env<'id>) -> ResourceTypeHandle<T> {
let ptr = registry(env).and_then(|r| r.get::<T>()).expect(
"resource type not registered — call otter::resource::register::<T> in your load callback",
);
ResourceTypeHandle { ptr, _t: PhantomData }
}
pub fn make_resource<'id, T: Resource>(env: impl Env<'id>, val: T) -> ResourceArc<T> {
resource_handle::<T>(env).make(val)
}
#[derive(Clone, Copy)]
pub struct Monitor(pub(crate) enif_ffi::Monitor);
impl Monitor {
pub fn to_term<'id>(self, env: impl Env<'id>) -> AnyTerm<'id> {
let raw = unsafe { enif_ffi::make_monitor_term(env.raw_env(), &self.0) };
AnyTerm::wrap(raw, env)
}
}
impl PartialEq for Monitor {
fn eq(&self, other: &Self) -> bool {
unsafe { enif_ffi::compare_monitors(&self.0, &other.0) == 0 }
}
}
impl Eq for Monitor {}
pub trait Resource: Sized + Send + Sync + 'static {
fn destructor(self, _env: CallbackEnv<'_>) {}
fn down<'a>(&'a self, _env: CallbackEnv<'a>, _pid: LocalPid, _monitor: Monitor) {}
fn stop(&self, _env: CallbackEnv<'_>, _event: enif_ffi::Event, _is_direct_call: bool) {}
}
fn absorb_callback_panic(what: &str, result: std::thread::Result<()>) {
if let Err(payload) = result {
let msg = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>");
eprintln!("otter: panic in resource {what} callback absorbed: {msg}");
}
}
unsafe extern "C" fn destructor_callback<T: Resource>(env: *mut enif_ffi::Env, obj: *mut c_void) {
let inner = align_ptr::<T>(obj);
let result = catch_unwind(AssertUnwindSafe(|| {
let val = unsafe { std::ptr::read(inner) };
unsafe { CallbackEnv::with_raw(env, |cenv| val.destructor(cenv)) };
}));
absorb_callback_panic("destructor", result);
}
unsafe extern "C" fn down_callback<T: Resource>(
env: *mut enif_ffi::Env,
obj: *mut c_void,
pid: *mut enif_ffi::Pid,
mon: *mut enif_ffi::Monitor,
) {
let inner = align_ptr::<T>(obj) as *const T;
let result = catch_unwind(AssertUnwindSafe(|| {
let pid = LocalPid { pid: unsafe { *pid } };
let monitor = Monitor(unsafe { *mon });
unsafe { CallbackEnv::with_raw(env, |cenv| (*inner).down(cenv, pid, monitor)) };
}));
absorb_callback_panic("down", result);
}
unsafe extern "C" fn stop_callback<T: Resource>(
env: *mut enif_ffi::Env,
obj: *mut c_void,
event: enif_ffi::Event,
is_direct_call: c_int,
) {
let inner = align_ptr::<T>(obj) as *const T;
let result = catch_unwind(AssertUnwindSafe(|| {
unsafe { CallbackEnv::with_raw(env, |cenv| (*inner).stop(cenv, event, is_direct_call != 0)) };
}));
absorb_callback_panic("stop", result);
}
pub use enif_ffi::ResourceFlags;
pub fn register<'id, T: Resource>(env: InitEnv<'id>, flags: ResourceFlags) {
let name = format!("{}#abi={:016x}", std::any::type_name::<T>(), crate::abi::tag());
register_named::<T>(env, flags, &name);
}
pub fn register_tagged<'id, T: Resource>(env: InitEnv<'id>, flags: ResourceFlags, tag: &str) {
let name = format!("{}#tag={}", std::any::type_name::<T>(), tag);
register_named::<T>(env, flags, &name);
}
fn register_named<'id, T: Resource>(env: InitEnv<'id>, flags: ResourceFlags, name: &str) {
let cname =
std::ffi::CString::new(name).expect("resource type name must not contain null bytes");
let init = enif_ffi::ResourceTypeInit {
dtor: Some(destructor_callback::<T>),
stop: Some(stop_callback::<T>),
down: Some(down_callback::<T>),
members: 3,
dyncall: None,
};
let mut tried = flags;
let type_ptr =
unsafe { enif_ffi::init_resource_type(env.raw_env(), cname.as_ptr(), &init, flags, &mut tried) };
assert!(
!type_ptr.is_null(),
"enif_init_resource_type failed — it must run inside load/upgrade, and the \
flags must match the name's state (CREATE needs a new name, TAKEOVER an \
existing one); a name collision under CREATE alone also returns null"
);
let pd = unsafe { enif_ffi::priv_data(env.raw_env()) } as *mut PrivData;
assert!(
!pd.is_null(),
"priv_data not installed — register must run inside otter's load scaffolding"
);
unsafe { (*pd).registry_mut().insert::<T>(type_ptr) };
}
pub struct ResourceArc<T: Resource> {
raw: *mut c_void,
inner: *mut T,
}
unsafe impl<T: Resource> Send for ResourceArc<T> {}
unsafe impl<T: Resource> Sync for ResourceArc<T> {}
fn align_ptr<T>(raw: *mut c_void) -> *mut T {
let align = std::mem::align_of::<T>();
let aligned = ((raw as usize) + align - 1) & !(align - 1);
aligned as *mut T
}
impl<T: Resource> ResourceArc<T> {
pub fn raw_ptr(&self) -> *mut c_void {
self.raw
}
pub fn monitor<'id, E: Env<'id>>(&self, env: Option<E>, pid: &LocalPid) -> Option<Monitor> {
let env_ptr = env.map(|e| e.raw_env()).unwrap_or(std::ptr::null_mut());
let mut mon: enif_ffi::Monitor = unsafe { std::mem::zeroed() };
let rc = unsafe { enif_ffi::monitor_process(env_ptr, self.raw, &pid.pid, &mut mon) };
if rc == 0 {
Some(Monitor(mon))
} else {
None
}
}
pub fn demonitor<'id, E: Env<'id>>(&self, env: Option<E>, mon: &Monitor) -> bool {
let env_ptr = env.map(|e| e.raw_env()).unwrap_or(std::ptr::null_mut());
unsafe { enif_ffi::demonitor_process(env_ptr, self.raw, &mon.0) == 0 }
}
}
impl<T: Resource> Clone for ResourceArc<T> {
fn clone(&self) -> ResourceArc<T> {
unsafe { enif_ffi::keep_resource(self.raw) };
ResourceArc { raw: self.raw, inner: self.inner }
}
}
impl<T: Resource> Drop for ResourceArc<T> {
fn drop(&mut self) {
unsafe { enif_ffi::release_resource(self.raw) };
}
}
impl<T: Resource> std::ops::Deref for ResourceArc<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.inner }
}
}
impl<'id, T: Resource> Encoder<'id> for ResourceArc<T> {
fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
let raw = unsafe { enif_ffi::make_resource(env.raw_env(), self.raw) };
Ok(AnyTerm::wrap(raw, env))
}
}
impl<'id, T: Resource> Decoder<'id> for ResourceArc<T> {
fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
let type_ptr = registry(env).and_then(|r| r.get::<T>()).ok_or(CodecError::WrongType)?;
let mut obj: *mut c_void = std::ptr::null_mut();
if unsafe { enif_ffi::get_resource(env.raw_env(), term.raw_term(), type_ptr, &mut obj) } == 0 {
return Err(CodecError::WrongType);
}
unsafe { enif_ffi::keep_resource(obj) };
Ok(ResourceArc { raw: obj, inner: align_ptr::<T>(obj) })
}
}
pub unsafe fn dynamic_resource_call<'id>(
env: impl Env<'id>,
mod_name: impl Term<'id>,
name: impl Term<'id>,
rsrc: impl Term<'id>,
call_data: *mut c_void,
) -> i32 {
unsafe {
enif_ffi::dynamic_resource_call(
env.raw_env(),
mod_name.raw_term(),
name.raw_term(),
rsrc.raw_term(),
call_data,
)
}
}