use crate::term::{NifError, NifResult};
use core::ffi::{c_void, c_char, c_int, c_uint};
use alloc::format;
use alloc::boxed::Box;
#[allow(non_camel_case_types)]
pub type ERL_NIF_TERM = u64;
pub type ErlNifEnv = c_void; pub type ErlNifResourceType = c_void; pub type ErlNifPid = i32;
pub type ErlNifEvent = c_int;
pub type ErlNifResourceDtor = unsafe extern "C" fn(caller_env: *mut ErlNifEnv, obj: *mut c_void);
pub type ErlNifResourceStop = unsafe extern "C" fn(
caller_env: *mut ErlNifEnv,
obj: *mut c_void,
event: ErlNifEvent,
is_direct_call: c_int
);
pub type ErlNifResourceDown = unsafe extern "C" fn(
caller_env: *mut ErlNifEnv,
obj: *mut c_void,
pid: *mut ErlNifPid,
mon: *mut ErlNifMonitor
);
#[repr(C)]
pub struct ErlNifMonitor {
pub resource_type: *mut ErlNifResourceType,
pub ref_ticks: u64,
}
#[repr(C)]
pub struct ErlNifResourceTypeInit {
pub members: c_int,
pub dtor: Option<ErlNifResourceDtor>,
pub stop: Option<ErlNifResourceStop>,
pub down: Option<ErlNifResourceDown>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(non_camel_case_types)]
pub enum ErlNifResourceFlags {
ERL_NIF_RT_CREATE = 1,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(non_camel_case_types)]
pub enum ErlNifSelectFlags {
ERL_NIF_SELECT_READ = 1,
ERL_NIF_SELECT_WRITE = 2,
ERL_NIF_SELECT_STOP = 4,
}
extern "C" {
pub fn enif_init_resource_type(
env: *mut ErlNifEnv,
name: *const c_char,
init: *const ErlNifResourceTypeInit,
flags: ErlNifResourceFlags,
tried: *mut ErlNifResourceFlags,
) -> *mut ErlNifResourceType;
pub fn enif_alloc_resource(
resource_type: *mut ErlNifResourceType,
size: c_uint,
) -> *mut c_void;
pub fn enif_make_resource(
env: *mut ErlNifEnv,
obj: *mut c_void,
) -> ERL_NIF_TERM;
pub fn enif_get_resource(
env: *mut ErlNifEnv,
t: ERL_NIF_TERM,
resource_type: *mut ErlNifResourceType,
objp: *mut *mut c_void,
) -> c_int;
pub fn enif_keep_resource(obj: *mut c_void) -> c_int;
pub fn enif_release_resource(obj: *mut c_void) -> c_int;
pub fn enif_select(
env: *mut ErlNifEnv,
event: ErlNifEvent,
mode: ErlNifSelectFlags,
obj: *mut c_void,
pid: *const ErlNifPid,
reference: ERL_NIF_TERM,
) -> c_int;
pub fn enif_monitor_process(
env: *mut ErlNifEnv,
obj: *mut c_void,
target_pid: *const ErlNifPid,
mon: *mut ErlNifMonitor,
) -> c_int;
pub fn enif_demonitor_process(
caller_env: *mut ErlNifEnv,
obj: *mut c_void,
mon: *const ErlNifMonitor,
) -> c_int;
}
#[derive(Debug, PartialEq, Clone)]
pub enum ResourceError {
InvalidName,
OutOfMemory,
BadResourceType,
BadArg,
InitializationFailed,
ResourceNotFound,
NotSupported,
}
impl From<ResourceError> for NifError {
fn from(err: ResourceError) -> Self {
match err {
ResourceError::OutOfMemory => NifError::OutOfMemory,
ResourceError::BadArg
| ResourceError::BadResourceType
| ResourceError::ResourceNotFound
| ResourceError::InvalidName
| ResourceError::InitializationFailed
| ResourceError::NotSupported => NifError::BadArg,
}
}
}
pub trait ResourceManager: Send + Sync {
fn init_resource_type(
&mut self,
env: *mut ErlNifEnv,
name: &str,
init: &ErlNifResourceTypeInit,
flags: ErlNifResourceFlags,
) -> Result<*mut ErlNifResourceType, ResourceError>;
fn alloc_resource(
&self,
resource_type: *mut ErlNifResourceType,
size: c_uint,
) -> Result<*mut c_void, ResourceError>;
fn make_resource(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
) -> Result<ERL_NIF_TERM, ResourceError>;
fn get_resource(
&self,
env: *mut ErlNifEnv,
term: ERL_NIF_TERM,
resource_type: *mut ErlNifResourceType,
) -> Result<*mut c_void, ResourceError>;
fn keep_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
fn release_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
fn select(
&self,
env: *mut ErlNifEnv,
event: ErlNifEvent,
mode: ErlNifSelectFlags,
obj: *mut c_void,
pid: *const ErlNifPid,
reference: ERL_NIF_TERM,
) -> Result<(), ResourceError>;
fn monitor_process(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
target_pid: *const ErlNifPid,
mon: *mut ErlNifMonitor,
) -> Result<(), ResourceError>;
fn demonitor_process(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
mon: *const ErlNifMonitor,
) -> Result<(), ResourceError>;
}
#[derive(Debug, Default)]
pub struct AtomVMResourceManager;
impl AtomVMResourceManager {
pub fn new() -> Self {
Self::default()
}
}
impl ResourceManager for AtomVMResourceManager {
fn init_resource_type(
&mut self,
env: *mut ErlNifEnv,
name: &str,
init: &ErlNifResourceTypeInit,
flags: ErlNifResourceFlags,
) -> Result<*mut ErlNifResourceType, ResourceError> {
if env.is_null() {
return Err(ResourceError::BadArg);
}
if name.is_empty() || name.len() > 255 {
return Err(ResourceError::InvalidName);
}
let name_cstr = format!("{}\0", name);
let mut tried_flags = flags;
let resource_type = unsafe {
enif_init_resource_type(
env,
name_cstr.as_ptr() as *const c_char,
init,
flags,
&mut tried_flags,
)
};
if resource_type.is_null() {
Err(ResourceError::InitializationFailed)
} else {
Ok(resource_type)
}
}
fn alloc_resource(
&self,
resource_type: *mut ErlNifResourceType,
size: c_uint,
) -> Result<*mut c_void, ResourceError> {
if resource_type.is_null() {
return Err(ResourceError::BadResourceType);
}
if size == 0 {
return Err(ResourceError::BadArg);
}
let ptr = unsafe { enif_alloc_resource(resource_type, size) };
if ptr.is_null() {
Err(ResourceError::OutOfMemory)
} else {
Ok(ptr)
}
}
fn make_resource(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
) -> Result<ERL_NIF_TERM, ResourceError> {
if env.is_null() || obj.is_null() {
return Err(ResourceError::BadArg);
}
let term = unsafe { enif_make_resource(env, obj) };
if term == 0 {
Err(ResourceError::BadArg)
} else {
Ok(term)
}
}
fn get_resource(
&self,
env: *mut ErlNifEnv,
term: ERL_NIF_TERM,
resource_type: *mut ErlNifResourceType,
) -> Result<*mut c_void, ResourceError> {
if env.is_null() || resource_type.is_null() {
return Err(ResourceError::BadArg);
}
let mut obj_ptr: *mut c_void = core::ptr::null_mut();
let success = unsafe {
enif_get_resource(env, term, resource_type, &mut obj_ptr)
};
if success != 0 && !obj_ptr.is_null() {
Ok(obj_ptr)
} else {
Err(ResourceError::ResourceNotFound)
}
}
fn keep_resource(&self, obj: *mut c_void) -> Result<(), ResourceError> {
if obj.is_null() {
return Err(ResourceError::BadArg);
}
let result = unsafe { enif_keep_resource(obj) };
if result != 0 {
Ok(())
} else {
Err(ResourceError::BadArg)
}
}
fn release_resource(&self, obj: *mut c_void) -> Result<(), ResourceError> {
if obj.is_null() {
return Err(ResourceError::BadArg);
}
let result = unsafe { enif_release_resource(obj) };
if result != 0 {
Ok(())
} else {
Err(ResourceError::BadArg)
}
}
fn select(
&self,
env: *mut ErlNifEnv,
event: ErlNifEvent,
mode: ErlNifSelectFlags,
obj: *mut c_void,
pid: *const ErlNifPid,
reference: ERL_NIF_TERM,
) -> Result<(), ResourceError> {
if env.is_null() || obj.is_null() || pid.is_null() {
return Err(ResourceError::BadArg);
}
let result = unsafe {
enif_select(env, event, mode, obj, pid, reference)
};
if result == 0 {
Ok(())
} else {
Err(ResourceError::BadArg)
}
}
fn monitor_process(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
target_pid: *const ErlNifPid,
mon: *mut ErlNifMonitor,
) -> Result<(), ResourceError> {
if env.is_null() || obj.is_null() || target_pid.is_null() || mon.is_null() {
return Err(ResourceError::BadArg);
}
let result = unsafe {
enif_monitor_process(env, obj, target_pid, mon)
};
if result == 0 {
Ok(())
} else {
Err(ResourceError::BadArg)
}
}
fn demonitor_process(
&self,
env: *mut ErlNifEnv,
obj: *mut c_void,
mon: *const ErlNifMonitor,
) -> Result<(), ResourceError> {
if env.is_null() || obj.is_null() || mon.is_null() {
return Err(ResourceError::BadArg);
}
let result = unsafe {
enif_demonitor_process(env, obj, mon)
};
if result == 0 {
Ok(())
} else {
Err(ResourceError::BadArg)
}
}
}
static mut RESOURCE_MANAGER: Option<Box<dyn ResourceManager>> = None;
static RESOURCE_MANAGER_INIT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
pub fn init_resource_manager<T: ResourceManager + 'static>(manager: T) {
unsafe {
RESOURCE_MANAGER = Some(Box::new(manager));
RESOURCE_MANAGER_INIT.store(true, core::sync::atomic::Ordering::SeqCst);
}
}
pub fn get_resource_manager() -> &'static dyn ResourceManager {
if !RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
panic!("Resource manager not initialized. Call init_resource_manager() first.");
}
unsafe {
RESOURCE_MANAGER.as_ref()
.expect("Resource manager should be initialized")
.as_ref()
}
}
pub unsafe fn get_resource_manager_mut() -> &'static mut dyn ResourceManager {
if !RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
panic!("Resource manager not initialized. Call init_resource_manager() first.");
}
RESOURCE_MANAGER.as_mut()
.expect("Resource manager should be initialized")
.as_mut()
}
pub const fn resource_type_init() -> ErlNifResourceTypeInit {
ErlNifResourceTypeInit {
members: 0,
dtor: None,
stop: None,
down: None,
}
}
pub const fn resource_type_init_with_dtor(dtor: ErlNifResourceDtor) -> ErlNifResourceTypeInit {
ErlNifResourceTypeInit {
members: 1,
dtor: Some(dtor),
stop: None,
down: None,
}
}
pub const fn resource_type_init_full(
dtor: Option<ErlNifResourceDtor>,
stop: Option<ErlNifResourceStop>,
down: Option<ErlNifResourceDown>,
) -> ErlNifResourceTypeInit {
let mut members = 0;
if dtor.is_some() { members += 1; }
if stop.is_some() { members += 1; }
if down.is_some() { members += 1; }
ErlNifResourceTypeInit {
members,
dtor,
stop,
down,
}
}
pub fn keep_resource(resource: *mut c_void) -> NifResult<()> {
if RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
let manager = get_resource_manager();
manager.keep_resource(resource).map_err(|e| e.into())
} else {
let result = unsafe { enif_keep_resource(resource) };
if result != 0 {
Ok(())
} else {
Err(NifError::BadArg)
}
}
}
pub fn release_resource(resource: *mut c_void) -> NifResult<()> {
if RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
let manager = get_resource_manager();
manager.release_resource(resource).map_err(|e| e.into())
} else {
let result = unsafe { enif_release_resource(resource) };
if result != 0 {
Ok(())
} else {
Err(NifError::BadArg)
}
}
}
#[macro_export]
macro_rules! resource_type {
($resource_name:ident, $rust_type:ty, $destructor_fn:ident) => {
static mut $resource_name: *mut $crate::resource::ErlNifResourceType = core::ptr::null_mut();
paste::paste! {
#[no_mangle]
pub extern "C" fn [<init_ $resource_name:lower>](env: *mut $crate::resource::ErlNifEnv) -> bool {
let resource_name_cstr = concat!(stringify!($resource_name), "\0");
let init_callbacks = $crate::resource::resource_type_init_with_dtor($destructor_fn);
let mut tried_flags = $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE;
unsafe {
$resource_name = $crate::resource::enif_init_resource_type(
env,
resource_name_cstr.as_ptr() as *const core::ffi::c_char,
&init_callbacks,
$crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE,
&mut tried_flags,
);
!$resource_name.is_null()
}
}
#[no_mangle]
pub extern "C" fn [<get_ $resource_name:lower>]() -> *mut $crate::resource::ErlNifResourceType {
unsafe { $resource_name }
}
}
};
($resource_name:ident, $rust_type:ty) => {
static mut $resource_name: *mut $crate::resource::ErlNifResourceType = core::ptr::null_mut();
paste::paste! {
#[no_mangle]
pub extern "C" fn [<init_ $resource_name:lower>](env: *mut $crate::resource::ErlNifEnv) -> bool {
let resource_name_cstr = concat!(stringify!($resource_name), "\0");
let init_callbacks = $crate::resource::resource_type_init();
let mut tried_flags = $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE;
unsafe {
$resource_name = $crate::resource::enif_init_resource_type(
env,
resource_name_cstr.as_ptr() as *const core::ffi::c_char,
&init_callbacks,
$crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE,
&mut tried_flags,
);
!$resource_name.is_null()
}
}
#[no_mangle]
pub extern "C" fn [<get_ $resource_name:lower>]() -> *mut $crate::resource::ErlNifResourceType {
unsafe { $resource_name }
}
}
};
}
#[macro_export]
macro_rules! create_resource {
($type_var:ident, $data:expr) => {{
let data = $data;
let size = core::mem::size_of_val(&data) as core::ffi::c_uint;
let ptr = unsafe {
paste::paste! {
extern "C" {
fn [<get_ $type_var:lower>]() -> *mut $crate::resource::ErlNifResourceType;
}
let resource_type = [<get_ $type_var:lower>]();
$crate::resource::enif_alloc_resource(resource_type, size)
}
};
if ptr.is_null() {
Err($crate::term::NifError::OutOfMemory)
} else {
unsafe {
core::ptr::write(ptr as *mut _, data);
}
Ok(ptr)
}
}};
}
#[macro_export]
macro_rules! get_resource {
($env:expr, $term:expr, $type_var:ident) => {{
let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut();
let success = unsafe {
paste::paste! {
extern "C" {
fn [<get_ $type_var:lower>]() -> *mut $crate::resource::ErlNifResourceType;
}
let resource_type = [<get_ $type_var:lower>]();
$crate::resource::enif_get_resource(
$env.as_c_ptr(),
$term.as_raw(),
resource_type,
&mut ptr as *mut *mut core::ffi::c_void,
)
}
};
if success != 0 && !ptr.is_null() {
Ok(unsafe { &mut *(ptr as *mut _) })
} else {
Err($crate::term::NifError::BadArg)
}
}};
}
#[macro_export]
macro_rules! make_resource_term {
($env:expr, $resource_ptr:expr) => {{
let raw_term = unsafe {
$crate::resource::enif_make_resource(
$env.as_c_ptr(),
$resource_ptr,
)
};
$crate::term::Term::from_raw(raw_term)
}};
}