use crate::{get_exception, qjs, Ctx, Error, Function, Mut, Ref, Result, Weak};
use std::{any::Any, ffi::CString, mem, panic};
#[cfg(feature = "futures")]
mod async_runtime;
#[cfg(feature = "futures")]
pub use async_runtime::*;
#[cfg(feature = "futures")]
mod async_executor;
#[cfg(feature = "futures")]
pub use self::async_executor::*;
#[cfg(feature = "registery")]
use crate::RegisteryKey;
#[cfg(feature = "registery")]
use fxhash::FxHashSet as HashSet;
pub use qjs::JSMemoryUsage as MemoryUsage;
#[cfg(feature = "allocator")]
use crate::{allocator::AllocatorHolder, Allocator};
#[cfg(feature = "loader")]
use crate::{loader::LoaderHolder, Loader, Resolver};
#[derive(Clone)]
#[repr(transparent)]
pub struct WeakRuntime(Weak<Mut<Inner>>);
impl WeakRuntime {
pub fn try_ref(&self) -> Option<Runtime> {
self.0.upgrade().map(|inner| Runtime { inner })
}
}
pub struct Opaque {
pub panic: Option<Box<dyn Any + Send + 'static>>,
pub interrupt_handler: Option<Box<dyn FnMut() -> bool + 'static>>,
pub runtime: WeakRuntime,
#[cfg(feature = "registery")]
pub registery: HashSet<RegisteryKey>,
#[cfg(feature = "futures")]
pub spawner: Option<Spawner>,
}
impl Opaque {
fn new(runtime: &Runtime) -> Self {
Opaque {
panic: None,
interrupt_handler: None,
runtime: runtime.weak(),
#[cfg(feature = "registery")]
registery: HashSet::default(),
#[cfg(feature = "futures")]
spawner: Default::default(),
}
}
}
pub(crate) struct Inner {
pub(crate) rt: *mut qjs::JSRuntime,
#[allow(dead_code)]
info: Option<CString>,
#[cfg(feature = "allocator")]
#[allow(dead_code)]
allocator: Option<AllocatorHolder>,
#[cfg(feature = "loader")]
#[allow(dead_code)]
loader: Option<LoaderHolder>,
}
impl Drop for Inner {
fn drop(&mut self) {
unsafe {
let ptr = qjs::JS_GetRuntimeOpaque(self.rt);
let opaque: Box<Opaque> = Box::from_raw(ptr as *mut _);
mem::drop(opaque);
qjs::JS_FreeRuntime(self.rt)
}
}
}
impl Inner {
pub(crate) fn update_stack_top(&self) {
#[cfg(feature = "parallel")]
unsafe {
qjs::JS_UpdateStackTop(self.rt);
}
}
#[cfg(feature = "futures")]
pub(crate) unsafe fn get_opaque(&self) -> &Opaque {
&*(qjs::JS_GetRuntimeOpaque(self.rt) as *const _)
}
pub(crate) unsafe fn get_opaque_mut(&mut self) -> &mut Opaque {
&mut *(qjs::JS_GetRuntimeOpaque(self.rt) as *mut _)
}
pub(crate) fn is_job_pending(&self) -> bool {
0 != unsafe { qjs::JS_IsJobPending(self.rt) }
}
pub(crate) fn execute_pending_job(&mut self) -> Result<bool> {
let mut ctx_ptr = mem::MaybeUninit::<*mut qjs::JSContext>::uninit();
self.update_stack_top();
let result = unsafe { qjs::JS_ExecutePendingJob(self.rt, ctx_ptr.as_mut_ptr()) };
if result == 0 {
return Ok(false);
}
let ctx_ptr = unsafe { ctx_ptr.assume_init() };
if result == 1 {
return Ok(true);
}
let ctx = Ctx::from_ptr(ctx_ptr);
Err(unsafe { get_exception(ctx) })
}
}
#[derive(Clone)]
#[repr(transparent)]
pub struct Runtime {
pub(crate) inner: Ref<Mut<Inner>>,
}
impl Runtime {
pub fn new() -> Result<Self> {
#[cfg(not(feature = "rust-alloc"))]
{
Self::new_raw(
unsafe { qjs::JS_NewRuntime() },
#[cfg(feature = "allocator")]
None,
)
}
#[cfg(feature = "rust-alloc")]
Self::new_with_alloc(crate::RustAllocator)
}
#[cfg(feature = "allocator")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "allocator")))]
pub fn new_with_alloc<A>(allocator: A) -> Result<Self>
where
A: Allocator + 'static,
{
let allocator = AllocatorHolder::new(allocator);
let functions = AllocatorHolder::functions::<A>();
let opaque = allocator.opaque_ptr();
Self::new_raw(
unsafe { qjs::JS_NewRuntime2(&functions, opaque as _) },
Some(allocator),
)
}
pub(crate) unsafe fn init_raw(rt: *mut qjs::JSRuntime) {
Function::init_raw(rt);
}
#[inline]
fn new_raw(
rt: *mut qjs::JSRuntime,
#[cfg(feature = "allocator")] allocator: Option<AllocatorHolder>,
) -> Result<Self> {
if rt.is_null() {
return Err(Error::Allocation);
}
unsafe { Self::init_raw(rt) };
let runtime = Runtime {
inner: Ref::new(Mut::new(Inner {
rt,
info: None,
#[cfg(feature = "allocator")]
allocator,
#[cfg(feature = "loader")]
loader: None,
})),
};
let opaque = Box::into_raw(Box::new(Opaque::new(&runtime)));
unsafe { qjs::JS_SetRuntimeOpaque(rt, opaque as *mut _) };
Ok(runtime)
}
pub fn weak(&self) -> WeakRuntime {
WeakRuntime(Ref::downgrade(&self.inner))
}
pub fn set_interrupt_handler(&self, handler: Option<Box<dyn FnMut() -> bool + 'static>>) {
unsafe extern "C" fn interrupt_handler_trampoline(
_rt: *mut qjs::JSRuntime,
opaque: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int {
let should_interrupt = match panic::catch_unwind(move || {
let opaque = &mut *(opaque as *mut Opaque);
opaque.interrupt_handler.as_mut().expect("handler is set")()
}) {
Ok(should_interrupt) => should_interrupt,
Err(panic) => {
let opaque = &mut *(opaque as *mut Opaque);
opaque.panic = Some(panic);
true
}
};
should_interrupt as _
}
let mut guard = self.inner.lock();
unsafe {
qjs::JS_SetInterruptHandler(
guard.rt,
handler.as_ref().map(|_| interrupt_handler_trampoline as _),
qjs::JS_GetRuntimeOpaque(guard.rt),
);
guard.get_opaque_mut().interrupt_handler = handler;
}
}
#[cfg(feature = "loader")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
pub fn set_loader<R, L>(&self, resolver: R, loader: L)
where
R: Resolver + 'static,
L: Loader + 'static,
{
let mut guard = self.inner.lock();
let loader = LoaderHolder::new(resolver, loader);
loader.set_to_runtime(guard.rt);
guard.loader = Some(loader);
}
pub fn set_info<S: Into<Vec<u8>>>(&self, info: S) -> Result<()> {
let mut guard = self.inner.lock();
let string = CString::new(info)?;
unsafe { qjs::JS_SetRuntimeInfo(guard.rt, string.as_ptr()) };
guard.info = Some(string);
Ok(())
}
pub fn set_memory_limit(&self, limit: usize) {
let guard = self.inner.lock();
unsafe { qjs::JS_SetMemoryLimit(guard.rt, limit as _) };
mem::drop(guard);
}
pub fn set_max_stack_size(&self, limit: usize) {
let guard = self.inner.lock();
unsafe { qjs::JS_SetMaxStackSize(guard.rt, limit as _) };
mem::drop(guard);
}
pub fn set_gc_threshold(&self, threshold: usize) {
let guard = self.inner.lock();
unsafe { qjs::JS_SetGCThreshold(guard.rt, threshold as _) };
mem::drop(guard);
}
pub fn run_gc(&self) {
let guard = self.inner.lock();
unsafe { qjs::JS_RunGC(guard.rt) };
mem::drop(guard);
}
pub fn memory_usage(&self) -> MemoryUsage {
let guard = self.inner.lock();
let mut stats = mem::MaybeUninit::uninit();
unsafe { qjs::JS_ComputeMemoryUsage(guard.rt, stats.as_mut_ptr()) };
mem::drop(guard);
unsafe { stats.assume_init() }
}
#[inline]
pub fn is_job_pending(&self) -> bool {
self.inner.lock().is_job_pending()
}
#[inline]
pub fn execute_pending_job(&self) -> Result<bool> {
self.inner.lock().execute_pending_job()
}
}
#[cfg(feature = "parallel")]
unsafe impl Send for Runtime {}
#[cfg(feature = "parallel")]
unsafe impl Send for WeakRuntime {}
#[cfg(feature = "parallel")]
unsafe impl Sync for Runtime {}
#[cfg(feature = "parallel")]
unsafe impl Sync for WeakRuntime {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn base_runtime() {
let rt = Runtime::new().unwrap();
rt.set_info("test runtime").unwrap();
rt.set_memory_limit(0xFFFF);
rt.set_gc_threshold(0xFF);
rt.run_gc();
}
}