use ::{ NifEnv, NifTerm };
use ::wrapper::nif_interface::{ self, NIF_ENV, NIF_TERM };
use ::types::pid::NifPid;
use std::ptr;
use std::sync::{Arc, Weak};
impl<'a> NifEnv<'a> {
/// Send a message to a process.
///
/// The Erlang VM imposes some odd restrictions on sending messages.
/// You can send messages in either of these situations:
///
/// * The current thread is managed by the Erlang VM, and `self` is the
/// environment of the calling process (that is, the environment that
/// Rustler passed in to your NIF); *or*
///
/// * The current thread is *not* managed by the Erlang VM.
///
/// # Panics
///
/// Panics if the above rules are broken (by trying to send a message from
/// an `OwnedEnv` on a thread that's managed by the Erlang VM).
///
pub fn send(self, pid: &NifPid, message: NifTerm<'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 {
// Panic if `self` is not the environment of the calling process.
self.pid();
self.as_c_arg()
} else {
panic!("NifEnv::send(): unrecognized calling thread type");
};
// Send the message.
unsafe {
nif_interface::enif_send(env, pid.as_c_arg(), ptr::null_mut(), message.as_c_arg());
}
}
}
/// A process-independent environment, a place where Erlang terms can be created outside of a NIF
/// call.
///
/// Rust code can use an owned environment to build a message and send it to an
/// Erlang process.
///
/// use rustler::env::OwnedEnv;
/// use rustler::types::pid::NifPid;
/// use rustler::NifEncoder;
///
/// fn send_string_to_pid(data: &str, pid: &NifPid) {
/// let mut msg_env = OwnedEnv::new();
/// msg_env.send_and_clear(pid, |env| data.encode(env));
/// }
///
/// There's no way to run Erlang code in an `OwnedEnv`. It's not a process. It's just a workspace
/// for building terms.
pub struct OwnedEnv {
env: Arc<NIF_ENV>
}
unsafe impl Send for OwnedEnv {}
impl OwnedEnv {
/// Allocates a new process-independent environment.
pub fn new() -> OwnedEnv {
OwnedEnv {
env: Arc::new(unsafe { nif_interface::enif_alloc_env() })
}
}
/// Run some code in this environment.
pub fn run<F, R>(&self, closure: F) -> R
where F: for<'a> FnOnce(NifEnv<'a>) -> R
{
let env_lifetime = ();
let env = unsafe { NifEnv::new(&env_lifetime, *self.env) };
closure(env)
}
/// Send a message from a Rust thread to an Erlang process.
///
/// The environment is cleared as though by calling the `.clear()` method.
/// To avoid that, use `env.send(pid, term)` instead.
///
/// # Panics
///
/// Panics if called from a thread that is managed by the Erlang VM. You
/// can only use this method on a thread that was created by other
/// means. (This curious restriction is imposed by the Erlang VM.)
///
pub fn send_and_clear<F>(&mut self, recipient: &NifPid, closure: F)
where F: for<'a> FnOnce(NifEnv<'a>) -> NifTerm<'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); // invalidate SavedTerms
unsafe {
nif_interface::enif_send(ptr::null_mut(), recipient.as_c_arg(), c_env, message);
}
}
/// Free all terms in this environment and clear it for reuse.
///
/// This invalidates `SavedTerm`s that were saved in this environment;
/// if you later try to `.load()` one, you'll get a panic.
///
/// Unless you call this method after a call to `run()`, all terms created within the
/// environment hang around in memory until the `OwnedEnv` is dropped: garbage collection does
/// not continually happen as needed in a NIF environment.
pub fn clear(&mut self) {
let c_env = *self.env;
self.env = Arc::new(c_env);
unsafe { nif_interface::enif_clear_env(c_env); }
}
/// Save a term for use in a later call to `.run()` or `.send()`.
///
/// For your safety, Rust doesn't let you save `NifTerm` values from one `.run()` call to a
/// later `.run()` call. If you try, it'll complain about lifetimes.
///
/// `.save()` offers a way to do this. For example, maybe you'd like to copy a term from the
/// caller into an `OwnedEnv`, then use that term on another thread.
///
/// # use rustler::{ NifEnv, NifTerm };
/// use rustler::env::OwnedEnv;
/// use std::thread;
///
/// fn thread_example<'a>(env: NifEnv<'a>, term: NifTerm<'a>) {
/// // Copy `term` into a new OwnedEnv, for use on another thread.
/// let mut thread_env = OwnedEnv::new();
/// let saved_term = thread_env.save(term);
///
/// thread::spawn(move || {
/// // Now run some code on the thread, using the saved term.
/// thread_env.run(|env| {
/// let term = saved_term.load(env);
/// //... do stuff with term ...
/// });
/// });
/// }
///
/// **Note: There is no way to save terms across `OwnedEnv::send()` or `clear()`.**
/// If you try, the `.load()` call will panic.
pub fn save<'a>(&self, term: NifTerm<'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); }
}
}
/// A term that was created in an `OwnedEnv` and saved for later use.
///
/// These are created by calling `OwnedEnv::save()`. See that method's documentation for an
/// example.
#[derive(Clone)]
pub struct SavedTerm {
env_generation: Weak<NIF_ENV>,
term: NIF_TERM,
}
unsafe impl Send for SavedTerm {}
impl SavedTerm {
/// Load this saved term back into its environment.
///
/// # Panics
///
/// `env` must be the `NifEnv` of a `.run()` or `.send()` call on the
/// `OwnedEnv` where this term was saved, and the `OwnedEnv` must not have
/// been cleared or dropped since then. Otherwise this method will panic.
pub fn load<'a>(&self, env: NifEnv<'a>) -> NifTerm<'a> {
// Check that the saved term is still valid.
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 { NifTerm::new(env, self.term) },
_ =>
panic!("can't load SavedTerm into a different environment"),
}
}
}