use std::marker::PhantomData;
use std::ptr;
use std::sync::{Arc, Weak};
use types::pid::Pid;
use wrapper::nif_interface::{self, NIF_ENV, NIF_TERM};
use {Encoder, Term};
type EnvId<'a> = PhantomData<*mut &'a u8>;
#[derive(Clone, Copy)]
pub struct Env<'a> {
env: NIF_ENV,
id: EnvId<'a>,
}
impl<'a, 'b> PartialEq<Env<'b>> for Env<'a> {
fn eq(&self, other: &Env<'b>) -> bool {
self.env == other.env
}
}
impl<'a> Env<'a> {
pub unsafe fn new<T>(_lifetime_marker: &'a T, env: NIF_ENV) -> Env<'a> {
Env {
env: env,
id: PhantomData,
}
}
pub fn as_c_arg(&self) -> NIF_ENV {
self.env
}
pub fn error_tuple<T>(self, reason: T) -> Term<'a>
where
T: Encoder,
{
let error = ::types::atom::error().to_term(self);
(error, reason).encode(self)
}
pub fn send(self, pid: &Pid, message: Term<'a>) {
let thread_type = nif_interface::enif_thread_type();
let env = if thread_type == nif_interface::ERL_NIF_THR_UNDEFINED {
ptr::null_mut()
} else if thread_type == nif_interface::ERL_NIF_THR_NORMAL_SCHEDULER
|| thread_type == nif_interface::ERL_NIF_THR_DIRTY_CPU_SCHEDULER
|| thread_type == nif_interface::ERL_NIF_THR_DIRTY_IO_SCHEDULER
{
self.pid();
self.as_c_arg()
} else {
panic!("Env::send(): unrecognized calling thread type");
};
unsafe {
nif_interface::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg());
}
}
pub fn binary_to_term(self, data: &[u8]) -> Option<(Term<'a>, usize)> {
unsafe {
::wrapper::env::binary_to_term(self.as_c_arg(), data, true)
.map(|(term, size)| (Term::new(self, term), size))
}
}
pub unsafe fn binary_to_term_trusted(self, data: &[u8]) -> Option<(Term<'a>, usize)> {
::wrapper::env::binary_to_term(self.as_c_arg(), data, false)
.map(|(term, size)| (Term::new(self, term), size))
}
}
pub struct OwnedEnv {
env: Arc<NIF_ENV>,
}
unsafe impl Send for OwnedEnv {}
impl OwnedEnv {
pub fn new() -> OwnedEnv {
OwnedEnv {
env: Arc::new(unsafe { nif_interface::enif_alloc_env() }),
}
}
pub fn run<F, R>(&self, closure: F) -> R
where
F: for<'a> FnOnce(Env<'a>) -> R,
{
let env_lifetime = ();
let env = unsafe { Env::new(&env_lifetime, *self.env) };
closure(env)
}
pub fn send_and_clear<F>(&mut self, recipient: &Pid, closure: F)
where
F: for<'a> FnOnce(Env<'a>) -> Term<'a>,
{
if nif_interface::enif_thread_type() != nif_interface::ERL_NIF_THR_UNDEFINED {
panic!("send_and_clear: current thread is managed");
}
let message = self.run(|env| closure(env).as_c_arg());
let c_env = *self.env;
self.env = Arc::new(c_env); unsafe {
nif_interface::enif_send(ptr::null_mut(), recipient.as_c_arg(), c_env, message);
}
}
pub fn clear(&mut self) {
let c_env = *self.env;
self.env = Arc::new(c_env);
unsafe {
nif_interface::enif_clear_env(c_env);
}
}
pub fn save<'a>(&self, term: Term<'a>) -> SavedTerm {
SavedTerm {
term: self.run(|env| term.in_env(env).as_c_arg()),
env_generation: Arc::downgrade(&self.env),
}
}
}
impl Drop for OwnedEnv {
fn drop(&mut self) {
unsafe {
nif_interface::enif_free_env(*self.env);
}
}
}
#[derive(Clone)]
pub struct SavedTerm {
env_generation: Weak<NIF_ENV>,
term: NIF_TERM,
}
unsafe impl Send for SavedTerm {}
impl SavedTerm {
pub fn load<'a>(&self, env: Env<'a>) -> Term<'a> {
match self.env_generation.upgrade() {
None => panic!("term is from a cleared or dropped OwnedEnv"),
Some(ref env_arc) if **env_arc == env.as_c_arg() => unsafe {
Term::new(env, self.term)
},
_ => panic!("can't load SavedTerm into a different environment"),
}
}
}