use crate::error::{check, materialize, RayError, Result};
use crate::value::Value;
use rayforce_sys as sys;
use std::cell::Cell;
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
static LIVE: AtomicBool = AtomicBool::new(false);
thread_local! {
static OWNS_RUNTIME: Cell<bool> = const { Cell::new(false) };
}
pub struct Runtime {
rt: *mut sys::ray_runtime_s,
_not_send: PhantomData<*mut ()>,
}
impl Runtime {
pub fn scope<F, R>(f: F) -> Result<R>
where
F: Send + FnOnce(&Runtime) -> Result<R>,
R: Send,
{
let rt = Runtime::new()?;
f(&rt)
}
fn new() -> Result<Runtime> {
if LIVE
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return Err(RayError::binding(if OWNS_RUNTIME.get() {
"a rayforce runtime is already live on this thread — \
Runtime::scope cannot be nested"
} else {
"a rayforce runtime is already live on another thread — \
the core permits one per process"
}));
}
let rt = unsafe { sys::ray_runtime_create(0, ptr::null_mut()) };
if rt.is_null() {
LIVE.store(false, Ordering::SeqCst);
return Err(RayError::binding("ray_runtime_create failed"));
}
OWNS_RUNTIME.set(true);
Ok(Runtime {
rt,
_not_send: PhantomData,
})
}
pub fn eval(&self, source: &str) -> Result<Value> {
eval(source)
}
pub fn set_global(&self, name: &str, value: &Value) -> Result<()> {
set_global(name, value)
}
pub fn get_global(&self, name: &str) -> Result<Value> {
get_global(name)
}
}
impl Drop for Runtime {
fn drop(&mut self) {
unsafe {
let poll = sys::ray_runtime_get_poll();
if !poll.is_null() {
sys::ray_runtime_set_poll(ptr::null_mut());
sys::ray_poll_destroy(poll.cast());
}
sys::ray_runtime_destroy(self.rt);
}
OWNS_RUNTIME.set(false);
LIVE.store(false, Ordering::SeqCst);
}
}
pub fn on_runtime_thread() -> bool {
OWNS_RUNTIME.get()
}
#[inline]
pub(crate) fn assert_on_runtime_thread(what: &str) {
if on_runtime_thread() {
return;
}
if LIVE.load(Ordering::SeqCst) {
panic!("rayforce: {what} called off the runtime's thread");
}
panic!("rayforce: {what} requires a live Runtime");
}
pub fn set_global(name: &str, value: &Value) -> Result<()> {
assert_on_runtime_thread("set_global");
unsafe {
let id = sys::ray_sym_intern(name.as_ptr() as *const _, name.len());
let e = sys::ray_env_set(id, value.as_ptr());
if e != sys::ray_err_t_RAY_OK {
return Err(RayError::binding(format!(
"set_global({name}) failed (err {e})"
)));
}
}
Ok(())
}
pub fn get_global(name: &str) -> Result<Value> {
assert_on_runtime_thread("get_global");
unsafe {
let id = sys::ray_sym_intern(name.as_ptr() as *const _, name.len());
let v = sys::ray_env_get(id);
if v.is_null() {
return Err(RayError::binding(format!("global not found: {name}")));
}
if crate::raw::is_err(v) {
return Err(RayError::from_obj(v));
}
Ok(Value::from_borrowed(v))
}
}
pub fn eval(source: &str) -> Result<Value> {
assert_on_runtime_thread("eval");
let c = CString::new(source).map_err(|_| RayError::binding("source contains a NUL byte"))?;
unsafe {
let r = sys::ray_eval_str(c.as_ptr());
if r.is_null() {
return Ok(Value::null());
}
Ok(Value::from_owned(materialize(check(r)?)?))
}
}
pub fn eval_value(obj: &Value) -> Result<Value> {
assert_on_runtime_thread("eval_value");
unsafe {
let r = sys::ray_eval(obj.as_ptr());
if r.is_null() {
return Ok(Value::null());
}
Ok(Value::from_owned(materialize(check(r)?)?))
}
}