use rustler::*;
use rustler::types::LocalPid;
use rustler::types::tuple::make_tuple;
use rustler::error::Error;
use rustler_sys;
use std::mem::MaybeUninit;
use rustler::wrapper::ErlNifPid;
use std::sync::{Mutex, Condvar};
use std::time::Duration;
use rustler_stored_term::StoredTerm;
use rustler_stored_term::StoredTerm::{AnAtom, Tuple};
use crate::ElixirFunCallResult::*;
pub struct ManualFuture {
mutex: Mutex<Option<StoredTerm>>,
cond: Condvar,
}
impl ManualFuture {
pub fn new() -> ManualFuture {
ManualFuture {mutex: Mutex::new(None), cond: Condvar::new()}
}
pub fn wait_until_filled(& self, timeout: Duration) -> Option<StoredTerm> {
let (mut guard, wait_timeout_result) = self.cond.wait_timeout_while(
self.mutex.lock().unwrap(),
timeout,
|pending| { pending.is_none() }
).expect("ManualFuture's Mutex was unexpectedly poisoned");
if wait_timeout_result.timed_out() {
None
} else {
let val = guard.take().unwrap();
Some(val)
}
}
pub fn fill(&self, value: StoredTerm) {
let mut started = self.mutex.lock().unwrap();
*started = Some(value);
self.cond.notify_all();
}
}
mod atoms {
rustler::atoms! {
ok,
error,
exception,
exit,
throw,
timeout,
}
}
pub fn whereis_pid<'a>(env: Env<'a>, name: Term<'a>) -> Result<LocalPid, Error> {
let mut enif_pid = MaybeUninit::uninit();
if unsafe { rustler_sys::enif_whereis_pid(env.as_c_arg(), name.as_c_arg(), enif_pid.as_mut_ptr()) } == 0 {
Err(Error::Term(Box::new("No pid registered under the given name.")))
} else {
let enif_pid = unsafe {enif_pid.assume_init()};
let pid = unsafe { std::mem::transmute::<ErlNifPid, LocalPid>(enif_pid) };
Ok(pid)
}
}
fn send_to_elixir<'a>(env: Env<'a>, pid: Term<'a>, value: Term<'a>) -> Result<(), Error> {
let pid : LocalPid = pid.decode().or_else(|_| whereis_pid(env, pid))?;
env.send(&pid, value);
Ok(())
}
#[derive(Clone)]
pub enum ElixirFunCallResult {
Success(StoredTerm),
ExceptionRaised(StoredTerm),
Exited(StoredTerm),
ValueThrown(StoredTerm),
TimedOut,
}
impl Encoder for ElixirFunCallResult {
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
let result = match self {
Success(term) => Ok(term),
ExceptionRaised(term) => Err(make_tuple(env, &[atoms::exception().to_term(env), term.encode(env)])),
Exited(term) => Err(make_tuple(env, &[atoms::exit().to_term(env), term.encode(env)])),
ValueThrown(term) => Err(make_tuple(env, &[atoms::throw().to_term(env), term.encode(env)])),
TimedOut => Err(atoms::timeout().to_term(env))
};
result.encode(env)
}
}
pub fn apply_elixir_fun<'a>(env: Env<'a>, pid_or_name: Term<'a>, fun: Term<'a>, parameters: Term<'a>) -> Result<ElixirFunCallResult, Error> {
apply_elixir_fun_timeout(env, pid_or_name, fun, parameters, Duration::from_millis(5000))
}
pub fn apply_elixir_fun_timeout<'a>(env: Env<'a>, pid_or_name: Term<'a>, fun: Term<'a>, parameters: Term<'a>, timeout: Duration) -> Result<ElixirFunCallResult, Error> {
if !fun.is_fun() {
return Err(Error::BadArg)
}
if !parameters.is_list() {
return Err(Error::BadArg)
}
let future = ManualFuture::new();
let future_ptr : *const ManualFuture = &future;
let raw_future_ptr = future_ptr as usize;
let fun_tuple = rustler::types::tuple::make_tuple(env, &[fun, parameters, raw_future_ptr.encode(env)]);
send_to_elixir(env, pid_or_name, fun_tuple)?;
match future.wait_until_filled(timeout) {
None => Ok(TimedOut),
Some(result) => Ok(parse_fun_call_result(env, result))
}
}
fn parse_fun_call_result<'a>(env: Env<'a>, result: StoredTerm) -> ElixirFunCallResult {
match result {
Tuple(ref tuple) =>
match &tuple[..] {
[AnAtom(ok), value] if ok == &rustler::types::atom::ok() => Success(value.clone()),
[AnAtom(error), Tuple(ref error_tuple)] if error == &atoms::error() => {
match &error_tuple[..] {
[AnAtom(exception), problem] if exception == &atoms::exception() => ExceptionRaised(problem.clone()),
[AnAtom(exit), problem] if exit == &atoms::exit() => Exited(problem.clone()),
[AnAtom(throw), problem] if throw == &atoms::throw() => ValueThrown(problem.clone()),
_ => panic!("RustlerElixirFun's function wrapper returned an unexpected error tuple result: {:?}", result.encode(env))
}
},
_ => panic!("RustlerElixirFun's function wrapper returned an unexpected tuple result: {:?}", result.encode(env))
},
_ => panic!("RustlerElixirFun's function wrapper returned an unexpected result: {:?}", result.encode(env))
}
}