use std::rc::Rc;
use std::sync::{Arc, Mutex};
use crate::error::ExecError;
use crate::native::local_send::{LocalSendError, LocalSendFacility, LocalSendRequest};
use crate::native::spawn::{SpawnError, SpawnFacility};
use crate::native::{NativeKey, ProcessContext, WasmAsyncNifFacility};
use crate::process::{ExitReason, Process};
use crate::replay::ReplayDriver;
use crate::term::Term;
use crate::timer::{TimerKind, TimerRef, TimerWheel};
pub type NativeHandlerFactory = Box<dyn Fn() -> Box<dyn NativeHandler> + Send + Sync>;
pub trait NativeHandler: Send + 'static {
fn handle(&mut self, ctx: &mut NativeContext<'_>) -> NativeOutcome;
}
pub enum NativeOutcome {
Continue,
Wait,
Stop(ExitReason),
}
pub(crate) struct NativeBody {
pub(crate) handler: Option<Box<dyn NativeHandler>>,
pub(crate) factory: NativeHandlerFactory,
}
impl NativeBody {
pub(crate) fn new(factory: NativeHandlerFactory) -> Self {
let handler = factory();
Self {
handler: Some(handler),
factory,
}
}
}
impl std::fmt::Debug for NativeBody {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("NativeBody")
.field("has_handler", &self.handler.is_some())
.finish()
}
}
pub struct NativeContext<'a> {
process: &'a mut Process,
local_send: Arc<dyn LocalSendFacility>,
spawn: Arc<dyn SpawnFacility>,
replay_driver: Option<Arc<Mutex<ReplayDriver>>>,
timers: Option<Arc<Mutex<TimerWheel>>>,
replay_error: Option<ExecError>,
wasm_async_nif_facility: Option<Rc<dyn WasmAsyncNifFacility>>,
}
impl<'a> NativeContext<'a> {
pub(crate) fn new(
process: &'a mut Process,
local_send: Arc<dyn LocalSendFacility>,
spawn: Arc<dyn SpawnFacility>,
replay_driver: Option<Arc<Mutex<ReplayDriver>>>,
timers: Option<Arc<Mutex<TimerWheel>>>,
) -> Self {
Self {
process,
local_send,
spawn,
replay_driver,
timers,
replay_error: None,
wasm_async_nif_facility: None,
}
}
pub(crate) fn set_wasm_async_nif_facility(
&mut self,
facility: Option<Rc<dyn WasmAsyncNifFacility>>,
) {
self.wasm_async_nif_facility = facility;
}
#[must_use]
pub fn self_pid(&self) -> u64 {
self.process.pid()
}
#[must_use]
pub fn has_messages(&self) -> bool {
!self.process.mailbox().is_empty()
}
#[must_use]
pub fn trap_exit(&self) -> bool {
self.process.trap_exit()
}
pub fn set_trap_exit(&mut self, value: bool) -> bool {
let previous = self.process.trap_exit();
self.process.set_trap_exit(value);
previous
}
pub fn recv(&mut self) -> Option<Term> {
let message = self.process.mailbox_mut().current_message()?;
let _removed = self.process.mailbox_mut().remove_current_message();
Some(message)
}
pub fn skip_message(&mut self) {
self.process.mailbox_mut().advance_save_pointer();
}
pub fn send(&mut self, target_pid: u64, message: Term) {
let previous_sender_clock = self.process.logical_clock();
let sender_clock = self.process.tick_logical_clock();
let sender_pid = self.process.pid();
let request = LocalSendRequest {
target_pid,
sender_pid,
message,
sender_clock,
replay_driver: self.replay_driver.as_ref(),
};
if let Err(LocalSendError::ReplayMismatch(detail)) = self.local_send.send_local(request) {
self.process.set_logical_clock(previous_sender_clock);
self.replay_error = Some(ExecError::ReplayMismatch(detail));
}
}
#[must_use]
pub fn alloc_tuple(&mut self, elements: &[Term]) -> Option<Term> {
let words = 1usize.checked_add(elements.len())?;
let slice = self.process.heap_mut().alloc_slice(words).ok()?;
crate::term::boxed::write_tuple(slice, elements)
}
#[must_use]
pub fn alloc_owned_term(&mut self, owned: &crate::ets::OwnedTerm) -> Option<Term> {
owned.copy_to_heap(self.process.heap_mut()).ok()
}
pub fn spawn_native(
&mut self,
factory: NativeHandlerFactory,
link_to: Option<u64>,
) -> Result<u64, SpawnError> {
self.spawn
.spawn_native(self.process.pid(), factory, link_to)
}
pub fn schedule(&mut self, delay: std::time::Duration, message: Term) -> Option<TimerRef> {
let target_pid = self.self_pid();
self.send_after(delay, target_pid, message)
}
pub fn send_after(
&mut self,
delay: std::time::Duration,
target_pid: u64,
message: Term,
) -> Option<TimerRef> {
let timers = self.timers.as_ref()?;
Some(
timers
.lock()
.unwrap_or_else(|error| error.into_inner())
.schedule(delay, target_pid, message, TimerKind::Deliver),
)
}
pub fn cancel_timer(&mut self, reference: TimerRef) -> Option<std::time::Duration> {
let timers = self.timers.as_ref()?;
timers
.lock()
.unwrap_or_else(|error| error.into_inner())
.cancel(reference)
}
pub fn start_async(&mut self, mfa: NativeKey, args: &[Term]) -> Result<(), Term> {
let Some(facility) = self.wasm_async_nif_facility.clone() else {
return Err(Term::atom(crate::atom::Atom::UNDEF));
};
let mut context = ProcessContext::new();
context.attach_process(self.process, 0);
context.set_current_native(Some(mfa));
context.set_wasm_async_nif_facility(Some(facility.clone()));
let result = facility.start_async_nif(mfa, args, &mut context);
context.detach_process();
result.map(|_started| ())
}
#[cfg(feature = "threads")]
pub(crate) fn take_replay_error(&mut self) -> Option<ExecError> {
self.replay_error.take()
}
}
#[cfg(test)]
mod tests {
use super::{NativeBody, NativeContext, NativeHandler, NativeOutcome};
use crate::process::Process;
struct Noop;
impl NativeHandler for Noop {
fn handle(&mut self, _ctx: &mut NativeContext<'_>) -> NativeOutcome {
NativeOutcome::Wait
}
}
fn noop_body() -> NativeBody {
NativeBody::new(Box::new(|| Box::new(Noop)))
}
#[test]
fn process_with_native_body_reports_is_native() {
let mut process = Process::new(7, 64);
assert!(
!process.is_native(),
"a fresh bytecode process is not native"
);
process.set_native_body(noop_body());
assert!(
process.is_native(),
"a process with a native body is native"
);
assert!(process.code_position().is_none());
assert_eq!(process.x_reg(0), crate::term::Term::NIL);
}
#[test]
fn structural_clone_is_non_native() {
let mut process = Process::new(7, 64);
process.set_native_body(noop_body());
assert!(process.is_native());
let clone = process.clone();
assert!(
!clone.is_native(),
"a structural clone must be non-native (handler not cloned)"
);
assert!(process.is_native(), "the original retains its handler");
}
}