use std::ffi::{CStr, CString};
use std::panic::{catch_unwind, AssertUnwindSafe};
use rayforce_sys as sys;
use crate::error::{RayError, Result};
use crate::raw;
use crate::value::Value;
pub struct Args<'a> {
raw: &'a [*mut sys::ray_t],
}
impl Args<'_> {
pub fn len(&self) -> usize {
self.raw.len()
}
pub fn is_empty(&self) -> bool {
self.raw.is_empty()
}
pub fn get(&self, i: usize) -> Option<Value> {
let ptr = *self.raw.get(i)?;
if ptr.is_null() {
return None;
}
Some(unsafe { Value::from_borrowed(ptr) })
}
}
pub trait VaryFn {
fn call(args: Args<'_>) -> Value;
}
pub trait UnaryFn {
fn call(arg: Value) -> Value;
}
fn guard(name: &str, f: impl FnOnce() -> Value) -> *mut sys::ray_t {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(v) => v.into_raw(),
Err(_) => {
eprintln!("rayforce: handler `{name}` panicked; replying null");
Value::null().into_raw()
}
}
}
extern "C" fn vary_trampoline<H: VaryFn>(args: *mut *mut sys::ray_t, n: i64) -> *mut sys::ray_t {
guard(std::any::type_name::<H>(), || {
let raw: &[*mut sys::ray_t] = if args.is_null() || n <= 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(args, n as usize) }
};
H::call(Args { raw })
})
}
extern "C" fn unary_trampoline<H: UnaryFn>(arg: *mut sys::ray_t) -> *mut sys::ray_t {
guard(std::any::type_name::<H>(), || {
let v = if arg.is_null() {
Value::null()
} else {
unsafe { Value::from_borrowed(arg) }
};
H::call(v)
})
}
fn bind_obj(name: &str, c: &CStr, f: *mut sys::ray_t) -> Result<()> {
if f.is_null() || unsafe { raw::is_err(f) } {
if !f.is_null() {
unsafe { sys::ray_release(f) };
}
return Err(RayError::binding(format!(
"could not build a function object for `{name}`"
)));
}
unsafe {
let id = sys::ray_sym_intern(c.as_ptr(), name.len());
let a = sys::ray_env_bind(id, f);
let b = sys::ray_env_bind_flat(id, f);
sys::ray_release(f);
if a != sys::ray_err_t_RAY_OK || b != sys::ray_err_t_RAY_OK {
return Err(RayError::binding(format!(
"could not bind `{name}` (rc {}, flat rc {})",
a as i64, b as i64
)));
}
}
Ok(())
}
pub fn bind_vary<H: VaryFn>(name: &str) -> Result<()> {
let c = bind_name(name)?;
let f = unsafe { sys::ray_fn_vary(c.as_ptr(), sys::RAY_FN_NONE, Some(vary_trampoline::<H>)) };
bind_obj(name, &c, f)
}
pub fn bind_unary<H: UnaryFn>(name: &str) -> Result<()> {
let c = bind_name(name)?;
let f = unsafe { sys::ray_fn_unary(c.as_ptr(), sys::RAY_FN_NONE, Some(unary_trampoline::<H>)) };
bind_obj(name, &c, f)
}
fn bind_name(name: &str) -> Result<CString> {
if !crate::runtime::on_runtime_thread() {
return Err(RayError::binding(
"binding a handler requires a live rayforce runtime on this thread",
));
}
if name.is_empty() {
return Err(RayError::binding("function name is empty"));
}
CString::new(name).map_err(|_| RayError::binding("function name contains NUL"))
}