avmnif_rs/
resource.rs

1//! Resource management macros for AtomVM NIFs
2//! 
3//! Provides safe Rust wrappers around AtomVM's resource NIF API with trait abstraction
4
5use crate::term::{NifError, NifResult};
6use core::ffi::{c_void, c_char, c_int, c_uint};
7use alloc::format;
8use alloc::boxed::Box;
9
10// Suppress naming warnings for FFI compatibility
11#[allow(non_camel_case_types)]
12pub type ERL_NIF_TERM = u64; // typedef term ERL_NIF_TERM (assuming 64-bit term)
13
14pub type ErlNifEnv = c_void; // Opaque struct
15pub type ErlNifResourceType = c_void; // Opaque struct  
16pub type ErlNifPid = i32;
17pub type ErlNifEvent = c_int;
18
19/// Resource destructor callback type
20pub type ErlNifResourceDtor = unsafe extern "C" fn(caller_env: *mut ErlNifEnv, obj: *mut c_void);
21
22/// Select stop callback type  
23pub type ErlNifResourceStop = unsafe extern "C" fn(
24    caller_env: *mut ErlNifEnv, 
25    obj: *mut c_void, 
26    event: ErlNifEvent, 
27    is_direct_call: c_int
28);
29
30/// Resource monitor callback type
31pub type ErlNifResourceDown = unsafe extern "C" fn(
32    caller_env: *mut ErlNifEnv, 
33    obj: *mut c_void, 
34    pid: *mut ErlNifPid, 
35    mon: *mut ErlNifMonitor
36);
37
38/// Monitor type
39#[repr(C)]
40pub struct ErlNifMonitor {
41    pub resource_type: *mut ErlNifResourceType,
42    pub ref_ticks: u64,
43}
44
45/// Resource type initialization callbacks
46#[repr(C)]
47pub struct ErlNifResourceTypeInit {
48    pub members: c_int,
49    pub dtor: Option<ErlNifResourceDtor>,
50    pub stop: Option<ErlNifResourceStop>, 
51    pub down: Option<ErlNifResourceDown>,
52}
53
54/// Resource creation flags
55#[repr(C)]
56#[derive(Debug, Clone, Copy, PartialEq)]
57#[allow(non_camel_case_types)]
58pub enum ErlNifResourceFlags {
59    ERL_NIF_RT_CREATE = 1,
60    // ERL_NIF_RT_TAKEOVER not supported yet
61}
62
63/// Select mode flags
64#[repr(C)]
65#[derive(Debug, Clone, Copy, PartialEq)]
66#[allow(non_camel_case_types)]
67pub enum ErlNifSelectFlags {
68    ERL_NIF_SELECT_READ = 1,
69    ERL_NIF_SELECT_WRITE = 2,
70    ERL_NIF_SELECT_STOP = 4,
71}
72
73// AtomVM Resource NIF FFI declarations (exact signatures from erl_nif.h)
74extern "C" {
75    /// Create or take over a resource type
76    pub fn enif_init_resource_type(
77        env: *mut ErlNifEnv,
78        name: *const c_char,
79        init: *const ErlNifResourceTypeInit,
80        flags: ErlNifResourceFlags,
81        tried: *mut ErlNifResourceFlags,
82    ) -> *mut ErlNifResourceType;
83
84    /// Allocate a new resource of the specified type and size
85    pub fn enif_alloc_resource(
86        resource_type: *mut ErlNifResourceType,
87        size: c_uint,
88    ) -> *mut c_void;
89
90    /// Create an Erlang term from a resource pointer
91    pub fn enif_make_resource(
92        env: *mut ErlNifEnv,
93        obj: *mut c_void,
94    ) -> ERL_NIF_TERM;
95
96    /// Extract a resource from an Erlang term
97    pub fn enif_get_resource(
98        env: *mut ErlNifEnv,
99        t: ERL_NIF_TERM,
100        resource_type: *mut ErlNifResourceType,
101        objp: *mut *mut c_void,
102    ) -> c_int;
103
104    /// Increment resource reference count
105    pub fn enif_keep_resource(obj: *mut c_void) -> c_int;
106
107    /// Decrement resource reference count
108    pub fn enif_release_resource(obj: *mut c_void) -> c_int;
109
110    /// Select on file descriptors  
111    pub fn enif_select(
112        env: *mut ErlNifEnv,
113        event: ErlNifEvent,
114        mode: ErlNifSelectFlags,
115        obj: *mut c_void,
116        pid: *const ErlNifPid,
117        reference: ERL_NIF_TERM,
118    ) -> c_int;
119
120    /// Monitor a process using a resource
121    pub fn enif_monitor_process(
122        env: *mut ErlNifEnv,
123        obj: *mut c_void,
124        target_pid: *const ErlNifPid,
125        mon: *mut ErlNifMonitor,
126    ) -> c_int;
127
128    /// Remove a process monitor
129    pub fn enif_demonitor_process(
130        caller_env: *mut ErlNifEnv,
131        obj: *mut c_void,
132        mon: *const ErlNifMonitor,
133    ) -> c_int;
134}
135
136/// Errors that can occur during resource operations
137#[derive(Debug, PartialEq, Clone)]
138pub enum ResourceError {
139    /// Resource name is invalid (empty or too long)
140    InvalidName,
141    /// Memory allocation failed
142    OutOfMemory,
143    /// Invalid resource type pointer
144    BadResourceType,
145    /// Invalid argument provided
146    BadArg,
147    /// Resource type initialization failed
148    InitializationFailed,
149    /// Resource not found or wrong type
150    ResourceNotFound,
151    /// Operation not supported
152    NotSupported,
153}
154
155impl From<ResourceError> for NifError {
156    fn from(err: ResourceError) -> Self {
157        match err {
158            ResourceError::OutOfMemory => NifError::OutOfMemory,
159            ResourceError::BadArg 
160            | ResourceError::BadResourceType 
161            | ResourceError::ResourceNotFound 
162            | ResourceError::InvalidName 
163            | ResourceError::InitializationFailed 
164            | ResourceError::NotSupported => NifError::BadArg,
165        }
166    }
167}
168
169/// Trait abstraction for resource management operations
170/// 
171/// This allows for dependency injection and makes the resource system testable
172/// while maintaining the same interface as the original FFI functions.
173pub trait ResourceManager: Send + Sync {
174    /// Initialize a new resource type
175    fn init_resource_type(
176        &mut self,
177        env: *mut ErlNifEnv,
178        name: &str,
179        init: &ErlNifResourceTypeInit,
180        flags: ErlNifResourceFlags,
181    ) -> Result<*mut ErlNifResourceType, ResourceError>;
182
183    /// Allocate memory for a new resource
184    fn alloc_resource(
185        &self,
186        resource_type: *mut ErlNifResourceType,
187        size: c_uint,
188    ) -> Result<*mut c_void, ResourceError>;
189
190    /// Create an Erlang term from a resource pointer
191    fn make_resource(
192        &self,
193        env: *mut ErlNifEnv,
194        obj: *mut c_void,
195    ) -> Result<ERL_NIF_TERM, ResourceError>;
196
197    /// Extract a resource pointer from an Erlang term
198    fn get_resource(
199        &self,
200        env: *mut ErlNifEnv,
201        term: ERL_NIF_TERM,
202        resource_type: *mut ErlNifResourceType,
203    ) -> Result<*mut c_void, ResourceError>;
204
205    /// Increment resource reference count
206    fn keep_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
207
208    /// Decrement resource reference count
209    fn release_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
210
211    /// Select on file descriptors for I/O readiness
212    fn select(
213        &self,
214        env: *mut ErlNifEnv,
215        event: ErlNifEvent,
216        mode: ErlNifSelectFlags,
217        obj: *mut c_void,
218        pid: *const ErlNifPid,
219        reference: ERL_NIF_TERM,
220    ) -> Result<(), ResourceError>;
221
222    /// Monitor a process for termination
223    fn monitor_process(
224        &self,
225        env: *mut ErlNifEnv,
226        obj: *mut c_void,
227        target_pid: *const ErlNifPid,
228        mon: *mut ErlNifMonitor,
229    ) -> Result<(), ResourceError>;
230
231    /// Remove a process monitor
232    fn demonitor_process(
233        &self,
234        env: *mut ErlNifEnv,
235        obj: *mut c_void,
236        mon: *const ErlNifMonitor,
237    ) -> Result<(), ResourceError>;
238}
239
240/// Production implementation using real AtomVM FFI calls
241#[derive(Debug, Default)]
242pub struct AtomVMResourceManager;
243
244impl AtomVMResourceManager {
245    /// Create a new AtomVM resource manager
246    pub fn new() -> Self {
247        Self::default()
248    }
249}
250
251impl ResourceManager for AtomVMResourceManager {
252    fn init_resource_type(
253        &mut self,
254        env: *mut ErlNifEnv,
255        name: &str,
256        init: &ErlNifResourceTypeInit,
257        flags: ErlNifResourceFlags,
258    ) -> Result<*mut ErlNifResourceType, ResourceError> {
259        // Validate input parameters
260        if env.is_null() {
261            return Err(ResourceError::BadArg);
262        }
263        if name.is_empty() || name.len() > 255 {
264            return Err(ResourceError::InvalidName);
265        }
266
267        // Ensure name is null-terminated for C FFI
268        let name_cstr = format!("{}\0", name);
269        let mut tried_flags = flags;
270        
271        let resource_type = unsafe {
272            enif_init_resource_type(
273                env,
274                name_cstr.as_ptr() as *const c_char,
275                init,
276                flags,
277                &mut tried_flags,
278            )
279        };
280
281        if resource_type.is_null() {
282            Err(ResourceError::InitializationFailed)
283        } else {
284            Ok(resource_type)
285        }
286    }
287
288    fn alloc_resource(
289        &self,
290        resource_type: *mut ErlNifResourceType,
291        size: c_uint,
292    ) -> Result<*mut c_void, ResourceError> {
293        if resource_type.is_null() {
294            return Err(ResourceError::BadResourceType);
295        }
296        if size == 0 {
297            return Err(ResourceError::BadArg);
298        }
299
300        let ptr = unsafe { enif_alloc_resource(resource_type, size) };
301        if ptr.is_null() {
302            Err(ResourceError::OutOfMemory)
303        } else {
304            Ok(ptr)
305        }
306    }
307
308    fn make_resource(
309        &self,
310        env: *mut ErlNifEnv,
311        obj: *mut c_void,
312    ) -> Result<ERL_NIF_TERM, ResourceError> {
313        if env.is_null() || obj.is_null() {
314            return Err(ResourceError::BadArg);
315        }
316
317        let term = unsafe { enif_make_resource(env, obj) };
318        if term == 0 {
319            Err(ResourceError::BadArg)
320        } else {
321            Ok(term)
322        }
323    }
324
325    fn get_resource(
326        &self,
327        env: *mut ErlNifEnv,
328        term: ERL_NIF_TERM,
329        resource_type: *mut ErlNifResourceType,
330    ) -> Result<*mut c_void, ResourceError> {
331        if env.is_null() || resource_type.is_null() {
332            return Err(ResourceError::BadArg);
333        }
334
335        let mut obj_ptr: *mut c_void = core::ptr::null_mut();
336        let success = unsafe {
337            enif_get_resource(env, term, resource_type, &mut obj_ptr)
338        };
339
340        if success != 0 && !obj_ptr.is_null() {
341            Ok(obj_ptr)
342        } else {
343            Err(ResourceError::ResourceNotFound)
344        }
345    }
346
347    fn keep_resource(&self, obj: *mut c_void) -> Result<(), ResourceError> {
348        if obj.is_null() {
349            return Err(ResourceError::BadArg);
350        }
351
352        let result = unsafe { enif_keep_resource(obj) };
353        if result != 0 {
354            Ok(())
355        } else {
356            Err(ResourceError::BadArg)
357        }
358    }
359
360    fn release_resource(&self, obj: *mut c_void) -> Result<(), ResourceError> {
361        if obj.is_null() {
362            return Err(ResourceError::BadArg);
363        }
364
365        let result = unsafe { enif_release_resource(obj) };
366        if result != 0 {
367            Ok(())
368        } else {
369            Err(ResourceError::BadArg)
370        }
371    }
372
373    fn select(
374        &self,
375        env: *mut ErlNifEnv,
376        event: ErlNifEvent,
377        mode: ErlNifSelectFlags,
378        obj: *mut c_void,
379        pid: *const ErlNifPid,
380        reference: ERL_NIF_TERM,
381    ) -> Result<(), ResourceError> {
382        if env.is_null() || obj.is_null() || pid.is_null() {
383            return Err(ResourceError::BadArg);
384        }
385
386        let result = unsafe {
387            enif_select(env, event, mode, obj, pid, reference)
388        };
389        if result == 0 {
390            Ok(())
391        } else {
392            Err(ResourceError::BadArg)
393        }
394    }
395
396    fn monitor_process(
397        &self,
398        env: *mut ErlNifEnv,
399        obj: *mut c_void,
400        target_pid: *const ErlNifPid,
401        mon: *mut ErlNifMonitor,
402    ) -> Result<(), ResourceError> {
403        if env.is_null() || obj.is_null() || target_pid.is_null() || mon.is_null() {
404            return Err(ResourceError::BadArg);
405        }
406
407        let result = unsafe {
408            enif_monitor_process(env, obj, target_pid, mon)
409        };
410        if result == 0 {
411            Ok(())
412        } else {
413            Err(ResourceError::BadArg)
414        }
415    }
416
417    fn demonitor_process(
418        &self,
419        env: *mut ErlNifEnv,
420        obj: *mut c_void,
421        mon: *const ErlNifMonitor,
422    ) -> Result<(), ResourceError> {
423        if env.is_null() || obj.is_null() || mon.is_null() {
424            return Err(ResourceError::BadArg);
425        }
426
427        let result = unsafe {
428            enif_demonitor_process(env, obj, mon)
429        };
430        if result == 0 {
431            Ok(())
432        } else {
433            Err(ResourceError::BadArg)
434        }
435    }
436}
437
438/// Global resource manager instance
439/// 
440/// This can be swapped out for testing or different implementations
441static mut RESOURCE_MANAGER: Option<Box<dyn ResourceManager>> = None;
442static RESOURCE_MANAGER_INIT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
443
444/// Initialize the global resource manager
445/// 
446/// This should be called once during NIF initialization
447pub fn init_resource_manager<T: ResourceManager + 'static>(manager: T) {
448    unsafe {
449        RESOURCE_MANAGER = Some(Box::new(manager));
450        RESOURCE_MANAGER_INIT.store(true, core::sync::atomic::Ordering::SeqCst);
451    }
452}
453
454/// Get a reference to the global resource manager
455/// 
456/// # Panics
457/// Panics if the resource manager hasn't been initialized
458pub fn get_resource_manager() -> &'static dyn ResourceManager {
459    if !RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
460        panic!("Resource manager not initialized. Call init_resource_manager() first.");
461    }
462    
463    unsafe {
464        RESOURCE_MANAGER.as_ref()
465            .expect("Resource manager should be initialized")
466            .as_ref()
467    }
468}
469
470/// Get a mutable reference to the global resource manager
471/// 
472/// # Safety
473/// This is unsafe because it provides mutable access to global state.
474/// Caller must ensure no other threads are accessing the manager.
475/// 
476/// # Panics  
477/// Panics if the resource manager hasn't been initialized
478pub unsafe fn get_resource_manager_mut() -> &'static mut dyn ResourceManager {
479    if !RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
480        panic!("Resource manager not initialized. Call init_resource_manager() first.");
481    }
482    
483    RESOURCE_MANAGER.as_mut()
484        .expect("Resource manager should be initialized")
485        .as_mut()
486}
487
488/// Helper for creating resource type initialization structs
489pub const fn resource_type_init() -> ErlNifResourceTypeInit {
490    ErlNifResourceTypeInit {
491        members: 0,
492        dtor: None,
493        stop: None,
494        down: None,
495    }
496}
497
498/// Helper for creating resource type initialization with destructor
499pub const fn resource_type_init_with_dtor(dtor: ErlNifResourceDtor) -> ErlNifResourceTypeInit {
500    ErlNifResourceTypeInit {
501        members: 1,
502        dtor: Some(dtor),
503        stop: None,
504        down: None,
505    }
506}
507
508/// Helper for creating resource type initialization with all callbacks
509pub const fn resource_type_init_full(
510    dtor: Option<ErlNifResourceDtor>,
511    stop: Option<ErlNifResourceStop>,
512    down: Option<ErlNifResourceDown>,
513) -> ErlNifResourceTypeInit {
514    let mut members = 0;
515    if dtor.is_some() { members += 1; }
516    if stop.is_some() { members += 1; }
517    if down.is_some() { members += 1; }
518    
519    ErlNifResourceTypeInit {
520        members,
521        dtor,
522        stop,
523        down,
524    }
525}
526
527/// Convenience functions that use the global resource manager or fallback to direct FFI
528/// Manually increment resource reference count
529pub fn keep_resource(resource: *mut c_void) -> NifResult<()> {
530    if RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
531        let manager = get_resource_manager();
532        manager.keep_resource(resource).map_err(|e| e.into())
533    } else {
534        // Fallback to direct FFI call if no manager is initialized
535        let result = unsafe { enif_keep_resource(resource) };
536        if result != 0 {
537            Ok(())
538        } else {
539            Err(NifError::BadArg)
540        }
541    }
542}
543
544/// Manually decrement resource reference count
545pub fn release_resource(resource: *mut c_void) -> NifResult<()> {
546    if RESOURCE_MANAGER_INIT.load(core::sync::atomic::Ordering::SeqCst) {
547        let manager = get_resource_manager();
548        manager.release_resource(resource).map_err(|e| e.into())
549    } else {
550        // Fallback to direct FFI call if no manager is initialized
551        let result = unsafe { enif_release_resource(resource) };
552        if result != 0 {
553            Ok(())
554        } else {
555            Err(NifError::BadArg)
556        }
557    }
558}
559
560/// Register a new resource type with AtomVM
561/// 
562/// # Usage
563/// ```rust,ignore
564/// use avmnif_rs::resource_type;
565/// 
566/// resource_type!(DISPLAY_TYPE, DisplayContext, display_destructor);
567/// ```
568#[macro_export]
569macro_rules! resource_type {
570    ($resource_name:ident, $rust_type:ty, $destructor_fn:ident) => {
571        // Create global static to hold the resource type pointer
572        static mut $resource_name: *mut $crate::resource::ErlNifResourceType = core::ptr::null_mut();
573        
574        // Create a module init function that registers this resource type
575        paste::paste! {
576            #[no_mangle]
577            pub extern "C" fn [<init_ $resource_name:lower>](env: *mut $crate::resource::ErlNifEnv) -> bool {
578                let resource_name_cstr = concat!(stringify!($resource_name), "\0");
579                let init_callbacks = $crate::resource::resource_type_init_with_dtor($destructor_fn);
580                let mut tried_flags = $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE;
581                
582                unsafe {
583                    $resource_name = $crate::resource::enif_init_resource_type(
584                        env,
585                        resource_name_cstr.as_ptr() as *const core::ffi::c_char,
586                        &init_callbacks,
587                        $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE,
588                        &mut tried_flags,
589                    );
590                    
591                    !$resource_name.is_null()
592                }
593            }
594            
595            // Provide a getter function for the resource type
596            #[no_mangle]
597            pub extern "C" fn [<get_ $resource_name:lower>]() -> *mut $crate::resource::ErlNifResourceType {
598                unsafe { $resource_name }
599            }
600        }
601    };
602    
603    // Version without destructor
604    ($resource_name:ident, $rust_type:ty) => {
605        // Create global static to hold the resource type pointer
606        static mut $resource_name: *mut $crate::resource::ErlNifResourceType = core::ptr::null_mut();
607        
608        paste::paste! {
609            #[no_mangle]
610            pub extern "C" fn [<init_ $resource_name:lower>](env: *mut $crate::resource::ErlNifEnv) -> bool {
611                let resource_name_cstr = concat!(stringify!($resource_name), "\0");
612                let init_callbacks = $crate::resource::resource_type_init();
613                let mut tried_flags = $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE;
614                
615                unsafe {
616                    $resource_name = $crate::resource::enif_init_resource_type(
617                        env,
618                        resource_name_cstr.as_ptr() as *const core::ffi::c_char,
619                        &init_callbacks,
620                        $crate::resource::ErlNifResourceFlags::ERL_NIF_RT_CREATE,
621                        &mut tried_flags,
622                    );
623                    
624                    !$resource_name.is_null()
625                }
626            }
627            
628            #[no_mangle]
629            pub extern "C" fn [<get_ $resource_name:lower>]() -> *mut $crate::resource::ErlNifResourceType {
630                unsafe { $resource_name }
631            }
632        }
633    };
634}
635
636/// Create a new resource instance
637/// 
638/// # Usage
639/// ```rust,ignore
640/// use avmnif_rs::create_resource;
641/// 
642/// let display_ptr = create_resource!(display_type, DisplayContext {
643///     width: 240,
644///     height: 320,
645///     initialized: true,
646/// })?;
647/// ```
648#[macro_export]
649macro_rules! create_resource {
650    ($type_var:ident, $data:expr) => {{
651        let data = $data;
652        let size = core::mem::size_of_val(&data) as core::ffi::c_uint;
653        let ptr = unsafe {
654            paste::paste! {
655                extern "C" {
656                    fn [<get_ $type_var:lower>]() -> *mut $crate::resource::ErlNifResourceType;
657                }
658                let resource_type = [<get_ $type_var:lower>]();
659                $crate::resource::enif_alloc_resource(resource_type, size)
660            }
661        };
662        if ptr.is_null() {
663            Err($crate::term::NifError::OutOfMemory)
664        } else {
665            // Write the data to the allocated resource
666            unsafe {
667                core::ptr::write(ptr as *mut _, data);
668            }
669            Ok(ptr)
670        }
671    }};
672}
673
674/// Extract a resource from an Erlang term
675/// 
676/// # Usage
677/// ```rust,ignore
678/// use avmnif_rs::get_resource;
679/// 
680/// let display = get_resource!(env, args[0], display_type)?;
681/// display.width = 320;
682/// ```
683#[macro_export]
684macro_rules! get_resource {
685    ($env:expr, $term:expr, $type_var:ident) => {{
686        let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut();
687        let success = unsafe {
688            paste::paste! {
689                extern "C" {
690                    fn [<get_ $type_var:lower>]() -> *mut $crate::resource::ErlNifResourceType;
691                }
692                let resource_type = [<get_ $type_var:lower>]();
693                $crate::resource::enif_get_resource(
694                    $env.as_c_ptr(),
695                    $term.as_raw(),
696                    resource_type,
697                    &mut ptr as *mut *mut core::ffi::c_void,
698                )
699            }
700        };
701        if success != 0 && !ptr.is_null() {
702            // SAFETY: Resource type system ensures this cast is valid
703            Ok(unsafe { &mut *(ptr as *mut _) })
704        } else {
705            Err($crate::term::NifError::BadArg)
706        }
707    }};
708}
709
710/// Convert a resource pointer to an Erlang term
711/// 
712/// # Usage
713/// ```rust,ignore
714/// avmnif_rs::make_resource_term;
715/// 
716/// let term = make_resource_term!(env, display_ptr);
717/// ```
718#[macro_export]
719macro_rules! make_resource_term {
720    ($env:expr, $resource_ptr:expr) => {{
721        let raw_term = unsafe {
722            $crate::resource::enif_make_resource(
723                $env.as_c_ptr(),
724                $resource_ptr,
725            )
726        };
727        $crate::term::Term::from_raw(raw_term)
728    }};
729}