1use crate::term::{NifError, NifResult};
6use core::ffi::{c_void, c_char, c_int, c_uint};
7use alloc::format;
8use alloc::boxed::Box;
9
10#[allow(non_camel_case_types)]
12pub type ERL_NIF_TERM = u64; pub type ErlNifEnv = c_void; pub type ErlNifResourceType = c_void; pub type ErlNifPid = i32;
17pub type ErlNifEvent = c_int;
18
19pub type ErlNifResourceDtor = unsafe extern "C" fn(caller_env: *mut ErlNifEnv, obj: *mut c_void);
21
22pub 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
30pub 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#[repr(C)]
40pub struct ErlNifMonitor {
41 pub resource_type: *mut ErlNifResourceType,
42 pub ref_ticks: u64,
43}
44
45#[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#[repr(C)]
56#[derive(Debug, Clone, Copy, PartialEq)]
57#[allow(non_camel_case_types)]
58pub enum ErlNifResourceFlags {
59 ERL_NIF_RT_CREATE = 1,
60 }
62
63#[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
73extern "C" {
75 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 pub fn enif_alloc_resource(
86 resource_type: *mut ErlNifResourceType,
87 size: c_uint,
88 ) -> *mut c_void;
89
90 pub fn enif_make_resource(
92 env: *mut ErlNifEnv,
93 obj: *mut c_void,
94 ) -> ERL_NIF_TERM;
95
96 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 pub fn enif_keep_resource(obj: *mut c_void) -> c_int;
106
107 pub fn enif_release_resource(obj: *mut c_void) -> c_int;
109
110 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 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 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#[derive(Debug, PartialEq, Clone)]
138pub enum ResourceError {
139 InvalidName,
141 OutOfMemory,
143 BadResourceType,
145 BadArg,
147 InitializationFailed,
149 ResourceNotFound,
151 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
169pub trait ResourceManager: Send + Sync {
174 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 fn alloc_resource(
185 &self,
186 resource_type: *mut ErlNifResourceType,
187 size: c_uint,
188 ) -> Result<*mut c_void, ResourceError>;
189
190 fn make_resource(
192 &self,
193 env: *mut ErlNifEnv,
194 obj: *mut c_void,
195 ) -> Result<ERL_NIF_TERM, ResourceError>;
196
197 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 fn keep_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
207
208 fn release_resource(&self, obj: *mut c_void) -> Result<(), ResourceError>;
210
211 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 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 fn demonitor_process(
233 &self,
234 env: *mut ErlNifEnv,
235 obj: *mut c_void,
236 mon: *const ErlNifMonitor,
237 ) -> Result<(), ResourceError>;
238}
239
240#[derive(Debug, Default)]
242pub struct AtomVMResourceManager;
243
244impl AtomVMResourceManager {
245 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 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 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
438static mut RESOURCE_MANAGER: Option<Box<dyn ResourceManager>> = None;
442static RESOURCE_MANAGER_INIT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
443
444pub 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
454pub 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
470pub 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
488pub const fn resource_type_init() -> ErlNifResourceTypeInit {
490 ErlNifResourceTypeInit {
491 members: 0,
492 dtor: None,
493 stop: None,
494 down: None,
495 }
496}
497
498pub 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
508pub 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
527pub 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 let result = unsafe { enif_keep_resource(resource) };
536 if result != 0 {
537 Ok(())
538 } else {
539 Err(NifError::BadArg)
540 }
541 }
542}
543
544pub 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 let result = unsafe { enif_release_resource(resource) };
552 if result != 0 {
553 Ok(())
554 } else {
555 Err(NifError::BadArg)
556 }
557 }
558}
559
560#[macro_export]
569macro_rules! resource_type {
570 ($resource_name:ident, $rust_type:ty, $destructor_fn:ident) => {
571 static mut $resource_name: *mut $crate::resource::ErlNifResourceType = core::ptr::null_mut();
573
574 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 #[no_mangle]
597 pub extern "C" fn [<get_ $resource_name:lower>]() -> *mut $crate::resource::ErlNifResourceType {
598 unsafe { $resource_name }
599 }
600 }
601 };
602
603 ($resource_name:ident, $rust_type:ty) => {
605 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#[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 unsafe {
667 core::ptr::write(ptr as *mut _, data);
668 }
669 Ok(ptr)
670 }
671 }};
672}
673
674#[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 Ok(unsafe { &mut *(ptr as *mut _) })
704 } else {
705 Err($crate::term::NifError::BadArg)
706 }
707 }};
708}
709
710#[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}