use crate::builder::QuickJsRuntimeBuilder;
use crate::esvalue::EsValueFacade;
use crate::quickjs_utils::{functions, objects};
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use crate::quickjsruntimeadapter::{
CompiledModuleLoaderAdapter, MemoryUsage, NativeModuleLoaderAdapter, QuickJsRuntimeAdapter,
ScriptModuleLoaderAdapter, QJS_RT,
};
use crate::reflection;
use crate::valueref::JSValueRef;
use hirofa_utils::eventloop::EventLoop;
use hirofa_utils::js_utils::adapters::{JsRealmAdapter, JsRuntimeAdapter};
use hirofa_utils::js_utils::facades::values::JsValueFacade;
use hirofa_utils::js_utils::facades::{JsRuntimeFacade, JsRuntimeFacadeInner};
use hirofa_utils::js_utils::JsError;
use hirofa_utils::js_utils::Script;
use hirofa_utils::task_manager::TaskManager;
use libquickjs_sys as q;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::{Arc, Weak};
use tokio::task::JoinError;
lazy_static! {
static ref HELPER_TASKS: TaskManager = TaskManager::new(std::cmp::max(2, num_cpus::get()));
}
impl Drop for QuickJsRuntimeFacade {
fn drop(&mut self) {
log::trace!("> EsRuntime::drop");
self.clear_contexts();
log::trace!("< EsRuntime::drop");
}
}
pub struct QuickjsRuntimeFacadeInner {
event_loop: EventLoop,
}
impl JsRuntimeFacadeInner for QuickjsRuntimeFacadeInner {
type JsRuntimeFacadeType = QuickJsRuntimeFacade;
fn js_exe_rt_task_in_event_loop<
R: Send + 'static,
J: FnOnce(&<<Self as JsRuntimeFacadeInner>::JsRuntimeFacadeType as JsRuntimeFacade>::JsRuntimeAdapterType) -> R + Send + 'static,
>(&self, task: J) -> R{
self.exe_rt_task_in_event_loop(task)
}
fn js_add_rt_task_to_event_loop<
R: Send + 'static,
J: FnOnce(&<<Self as JsRuntimeFacadeInner>::JsRuntimeFacadeType as JsRuntimeFacade>::JsRuntimeAdapterType) -> R + Send + 'static,
>(&self, task: J) -> Pin<Box<dyn Future<Output=R> + Send>>{
Box::pin(self.add_rt_task_to_event_loop(task))
}
fn js_add_rt_task_to_event_loop_void<J: FnOnce(&<<Self as JsRuntimeFacadeInner>::JsRuntimeFacadeType as JsRuntimeFacade>::JsRuntimeAdapterType) + Send + 'static>(&self, task: J){
self.add_rt_task_to_event_loop_void(task)
}
}
impl QuickjsRuntimeFacadeInner {
pub fn add_task_to_event_loop_void<C>(&self, task: C)
where
C: FnOnce() + Send + 'static,
{
self.event_loop.add_void(move || {
task();
EventLoop::add_local_void(|| {
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.run_pending_jobs_if_any();
})
})
});
}
pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
where
C: FnOnce() -> R + Send + 'static,
{
self.event_loop.exe(move || {
let res = task();
EventLoop::add_local_void(|| {
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.run_pending_jobs_if_any();
})
});
res
})
}
pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
where
C: FnOnce() -> R + Send + 'static,
{
self.event_loop.add(move || {
let res = task();
EventLoop::add_local_void(|| {
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.run_pending_jobs_if_any();
});
});
res
})
}
pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
&self,
consumer: C,
) -> impl Future<Output = R>
where
C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
{
self.add_task_to_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
}
pub fn add_rt_task_to_event_loop_void<C>(&self, consumer: C)
where
C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
{
self.add_task_to_event_loop_void(|| QuickJsRuntimeAdapter::do_with(consumer))
}
pub fn exe_rt_task_in_event_loop<C, R: Send + 'static>(&self, consumer: C) -> R
where
C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
{
self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
}
#[allow(dead_code)]
pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
where
C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
{
EventLoop::add_local_void(move || {
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
consumer(q_js_rt);
});
EventLoop::add_local_void(|| {
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
q_js_rt.run_pending_jobs_if_any();
})
})
});
}
}
pub struct QuickJsRuntimeFacade {
inner: Arc<QuickjsRuntimeFacadeInner>,
}
impl QuickJsRuntimeFacade {
pub(crate) fn new(mut builder: QuickJsRuntimeBuilder) -> Self {
let ret = Self {
inner: Arc::new(QuickjsRuntimeFacadeInner {
event_loop: EventLoop::new(),
}),
};
ret.exe_task_in_event_loop(|| {
let rt_ptr = unsafe { q::JS_NewRuntime() };
let rt = QuickJsRuntimeAdapter::new(rt_ptr);
QuickJsRuntimeAdapter::init_rt_for_current_thread(rt);
functions::init_statics();
reflection::init_statics();
});
let rti_weak = Arc::downgrade(&ret.inner);
ret.exe_task_in_event_loop(move || {
QuickJsRuntimeAdapter::do_with_mut(move |m_q_js_rt| {
m_q_js_rt.init_rti_ref(rti_weak);
})
});
#[cfg(any(
feature = "settimeout",
feature = "setinterval",
feature = "console",
feature = "setimmediate"
))]
{
let res = crate::features::init(&ret);
if res.is_err() {
panic!("could not init features: {}", res.err().unwrap());
}
}
if let Some(interval) = builder.opt_gc_interval {
let rti_ref: Weak<QuickjsRuntimeFacadeInner> = Arc::downgrade(&ret.inner);
std::thread::spawn(move || loop {
std::thread::sleep(interval);
if let Some(el) = rti_ref.upgrade() {
log::trace!("running gc from gc interval thread");
el.event_loop.add_void(|| {
QJS_RT
.try_with(|rc| {
let rt = &*rc.borrow();
rt.as_ref().unwrap().gc();
})
.expect("QJS_RT.try_with failed");
});
} else {
break;
}
});
}
let init_hooks: Vec<_> = builder.runtime_init_hooks.drain(..).collect();
ret.exe_task_in_event_loop(|| {
QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
for native_module_loader in builder.native_module_loaders {
q_js_rt.add_native_module_loader(NativeModuleLoaderAdapter::new(
native_module_loader,
));
}
for script_module_loader in builder.script_module_loaders {
q_js_rt.add_script_module_loader(ScriptModuleLoaderAdapter::new(
script_module_loader,
));
}
for compiled_module_loader in builder.compiled_module_loaders {
q_js_rt.add_compiled_module_loader(CompiledModuleLoaderAdapter::new(
compiled_module_loader,
));
}
q_js_rt.script_pre_processors = builder.script_pre_processors;
if let Some(limit) = builder.opt_memory_limit_bytes {
unsafe {
q::JS_SetMemoryLimit(q_js_rt.runtime, limit as _);
}
}
if let Some(threshold) = builder.opt_gc_threshold {
unsafe {
q::JS_SetGCThreshold(q_js_rt.runtime, threshold as _);
}
}
if let Some(stack_size) = builder.opt_max_stack_size {
unsafe {
q::JS_SetMaxStackSize(q_js_rt.runtime, stack_size as _);
}
}
if let Some(interrupt_handler) = builder.interrupt_handler {
q_js_rt.set_interrupt_handler(interrupt_handler);
}
})
});
for hook in init_hooks {
match hook(&ret) {
Ok(_) => {}
Err(e) => {
panic!("runtime_init_hook failed: {}", e);
}
}
}
ret
}
pub async fn memory_usage(&self) -> MemoryUsage {
self.js_loop(|rt| rt.memory_usage()).await
}
pub(crate) fn clear_contexts(&self) {
log::trace!("EsRuntime::clear_contexts");
self.exe_task_in_event_loop(|| {
let context_ids = QuickJsRuntimeAdapter::get_context_ids();
for id in context_ids {
QuickJsRuntimeAdapter::remove_context(id.as_str());
}
});
}
pub fn add_task_to_event_loop_void<C>(&self, task: C)
where
C: FnOnce() + Send + 'static,
{
self.inner.add_task_to_event_loop_void(task)
}
pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
where
C: FnOnce() -> R + Send + 'static,
{
self.inner.exe_task_in_event_loop(task)
}
pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
where
C: FnOnce() -> R + Send + 'static,
{
self.inner.add_task_to_event_loop(task)
}
pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
&self,
task: C,
) -> impl Future<Output = R>
where
C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
{
self.inner.add_rt_task_to_event_loop(task)
}
pub fn add_rt_task_to_event_loop_void<C>(&self, task: C)
where
C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
{
self.inner.add_rt_task_to_event_loop_void(task)
}
#[allow(dead_code)]
pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
where
C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
{
QuickjsRuntimeFacadeInner::add_local_task_to_event_loop(consumer)
}
pub fn builder() -> QuickJsRuntimeBuilder {
QuickJsRuntimeBuilder::new()
}
pub async fn eval(&self, script: Script) -> Result<EsValueFacade, JsError> {
self.add_rt_task_to_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let res = q_ctx.eval(script);
match res {
Ok(js) => EsValueFacade::from_jsval(q_ctx, &js),
Err(e) => Err(e),
}
})
.await
}
pub fn eval_sync(&self, script: Script) -> Result<EsValueFacade, JsError> {
self.exe_rt_task_in_event_loop(move |q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let res = q_ctx.eval(script);
match res {
Ok(val_ref) => EsValueFacade::from_jsval(q_ctx, &val_ref),
Err(e) => Err(e),
}
})
}
pub async fn gc(&self) {
self.add_rt_task_to_event_loop(|q_js_rt| q_js_rt.gc()).await
}
pub fn gc_sync(&self) {
self.exe_rt_task_in_event_loop(|q_js_rt| q_js_rt.gc())
}
pub fn call_function_sync(
&self,
namespace: Vec<&'static str>,
func_name: &str,
mut arguments: Vec<EsValueFacade>,
) -> Result<EsValueFacade, JsError> {
let func_name_string = func_name.to_string();
self.exe_rt_task_in_event_loop(move |q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let mut q_args = vec![];
for arg in &mut arguments {
q_args.push(arg.as_js_value(q_ctx)?);
}
let res = q_ctx.call_function(namespace, func_name_string.as_str(), q_args);
match res {
Ok(val_ref) => EsValueFacade::from_jsval(q_ctx, &val_ref),
Err(e) => Err(e),
}
})
}
pub async fn call_function(
&self,
namespace: Vec<&'static str>,
func_name: String,
mut arguments: Vec<EsValueFacade>,
) -> Result<EsValueFacade, JsError> {
let func_name_string = func_name.to_string();
self.add_rt_task_to_event_loop(move |q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let mut q_args = vec![];
for arg in &mut arguments {
match arg.as_js_value(q_ctx) {
Ok(js_arg) => q_args.push(js_arg),
Err(err) => log::error!(
"error occurred in async esruntime::call_function closure: {}",
err
),
}
}
let res = q_ctx.call_function(namespace, func_name_string.as_str(), q_args);
match res {
Ok(js_ref) => EsValueFacade::from_jsval(q_ctx, &js_ref),
Err(e) => Err(e),
}
})
.await
}
pub async fn eval_module(&self, script: Script) {
self.add_rt_task_to_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let res = q_ctx.eval_module(script);
match res {
Ok(_) => {}
Err(e) => log::error!("error in async eval {}", e),
}
})
.await
}
pub fn eval_module_sync(&self, script: Script) -> Result<EsValueFacade, JsError> {
self.exe_rt_task_in_event_loop(move |q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let res = q_ctx.eval_module(script);
match res {
Ok(val_ref) => EsValueFacade::from_jsval(q_ctx, &val_ref),
Err(e) => Err(e),
}
})
}
pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
where
C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
R: Send + 'static,
{
self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
}
pub fn set_function<F>(
&self,
namespace: Vec<&'static str>,
name: &str,
function: F,
) -> Result<(), JsError>
where
F: Fn(&QuickJsRealmAdapter, Vec<EsValueFacade>) -> Result<EsValueFacade, JsError>
+ Send
+ 'static,
{
let name = name.to_string();
self.exe_rt_task_in_event_loop(move |q_js_rt| {
let func_rc = Rc::new(function);
let name = name.to_string();
q_js_rt.add_context_init_hook(move |_q_js_rt, q_ctx| {
let ns = objects::get_namespace_q(q_ctx, namespace.clone(), true)?;
let func_rc = func_rc.clone();
let func = functions::new_function_q(
q_ctx,
name.as_str(),
move |q_ctx, _this_ref, args| {
let mut args_facades = vec![];
for arg_ref in args {
args_facades.push(EsValueFacade::from_jsval(q_ctx, arg_ref)?);
}
let res = func_rc(q_ctx, args_facades);
match res {
Ok(mut val_esvf) => val_esvf.as_js_value(q_ctx),
Err(e) => Err(e),
}
},
1,
)?;
objects::set_property2_q(q_ctx, &ns, name.as_str(), &func, 0)?;
Ok(())
})
})
}
pub fn add_helper_task<T>(task: T)
where
T: FnOnce() + Send + 'static,
{
log::trace!("adding a helper task");
HELPER_TASKS.add_task(task);
}
pub fn add_helper_task_async<R: Send + 'static, T: Future<Output = R> + Send + 'static>(
task: T,
) -> impl Future<Output = Result<R, JoinError>> {
log::trace!("adding an async helper task");
HELPER_TASKS.add_task_async(task)
}
pub fn create_context(&self, id: &str) -> Result<(), JsError> {
let id = id.to_string();
self.inner
.event_loop
.exe(move || QuickJsRuntimeAdapter::create_context(id.as_str()))
}
pub fn drop_context(&self, id: &str) {
let id = id.to_string();
self.inner
.event_loop
.exe(move || QuickJsRuntimeAdapter::remove_context(id.as_str()))
}
}
fn loop_realm_func<
R: Send + 'static,
C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
>(
realm_name: Option<String>,
consumer: C,
) -> R {
let res = QuickJsRuntimeAdapter::do_with(|q_js_rt| {
if let Some(realm_str) = realm_name.as_ref() {
if let Some(realm) = q_js_rt.js_get_realm(realm_str) {
(Some(consumer(q_js_rt, realm)), None)
} else {
(None, Some(consumer))
}
} else {
(Some(consumer(q_js_rt, q_js_rt.js_get_main_realm())), None)
}
});
if let Some(res) = res.0 {
res
} else {
let consumer = res.1.unwrap();
let realm_str = realm_name.expect("invalid state");
QuickJsRuntimeAdapter::do_with_mut(|m_rt| {
let ctx = QuickJsRealmAdapter::new(realm_str.to_string(), m_rt);
m_rt.contexts.insert(realm_str.to_string(), ctx);
});
QuickJsRuntimeAdapter::do_with(|q_js_rt| {
let realm = q_js_rt
.js_get_realm(realm_str.as_str())
.expect("invalid state");
let hooks = &*q_js_rt.context_init_hooks.borrow();
for hook in hooks {
let res = hook(q_js_rt, realm);
if res.is_err() {
panic!("realm init hook failed: {}", res.err().unwrap());
}
}
consumer(q_js_rt, realm)
})
}
}
impl JsRuntimeFacade for QuickJsRuntimeFacade {
type JsRuntimeAdapterType = QuickJsRuntimeAdapter;
type JsRuntimeFacadeInnerType = QuickjsRuntimeFacadeInner;
fn js_get_runtime_facade_inner(&self) -> Weak<QuickjsRuntimeFacadeInner> {
Arc::downgrade(&self.inner)
}
fn js_realm_create(&self, name: &str) -> Result<(), JsError> {
let name = name.to_string();
self.inner
.event_loop
.exe(move || QuickJsRuntimeAdapter::create_context(name.as_str()))
}
fn js_realm_destroy(&self, name: &str) -> Result<(), JsError> {
let name = name.to_string();
self.exe_task_in_event_loop(move || {
QuickJsRuntimeAdapter::do_with_mut(|rt| {
if rt.js_get_realm(name.as_str()).is_some() {
rt.js_remove_realm(name.as_str());
}
Ok(())
})
})
}
fn js_realm_has(&self, name: &str) -> Result<bool, JsError> {
let name = name.to_string();
self.exe_rt_task_in_event_loop(move |rt| Ok(rt.js_get_realm(name.as_str()).is_some()))
}
fn js_loop_sync<R: Send + 'static, C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static>(
&self,
consumer: C,
) -> R {
self.exe_rt_task_in_event_loop(consumer)
}
fn js_loop_sync_mut<
R: Send + 'static,
C: FnOnce(&mut Self::JsRuntimeAdapterType) -> R + Send + 'static,
>(
&self,
consumer: C,
) -> R {
self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with_mut(consumer))
}
fn js_loop<R: Send + 'static, C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static>(
&self,
consumer: C,
) -> Pin<Box<dyn Future<Output = R> + Send>> {
Box::pin(self.add_rt_task_to_event_loop(consumer))
}
fn js_loop_void<C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static>(&self, consumer: C) {
self.add_rt_task_to_event_loop_void(consumer)
}
fn js_loop_realm_sync<
R: Send + 'static,
C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
>(
&self,
realm_name: Option<&str>,
consumer: C,
) -> R {
let realm_name = realm_name.map(|s| s.to_string());
self.exe_task_in_event_loop(|| loop_realm_func(realm_name, consumer))
}
fn js_loop_realm<
R: Send + 'static,
C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
>(
&self,
realm_name: Option<&str>,
consumer: C,
) -> Pin<Box<dyn Future<Output = R>>> {
let realm_name = realm_name.map(|s| s.to_string());
Box::pin(self.add_task_to_event_loop(|| loop_realm_func(realm_name, consumer)))
}
fn js_loop_realm_void<
C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) + Send + 'static,
>(
&self,
realm_name: Option<&str>,
consumer: C,
) {
let realm_name = realm_name.map(|s| s.to_string());
self.add_task_to_event_loop_void(|| loop_realm_func(realm_name, consumer));
}
#[allow(clippy::type_complexity)]
fn js_eval(
&self,
realm_name: Option<&str>,
script: Script,
) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
self.js_loop_realm(realm_name, |_rt, realm| {
let res = realm.js_eval(script);
match res {
Ok(jsvr) => realm.to_js_value_facade(&jsvr),
Err(e) => Err(e),
}
})
}
fn js_eval_module(
&self,
realm_name: Option<&str>,
script: Script,
) -> Pin<Box<dyn Future<Output = Result<(), JsError>>>> {
self.js_loop_realm(realm_name, |_rt, realm| {
let res = realm.js_eval_module(script);
res.map(|_| ())
})
}
#[warn(clippy::type_complexity)]
fn js_function_invoke_sync(
&self,
realm_name: Option<&str>,
namespace: &[&str],
method_name: &str,
args: Vec<JsValueFacade>,
) -> Result<JsValueFacade, JsError> {
let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
let movable_method_name = method_name.to_string();
self.js_loop_realm_sync(realm_name, move |_rt, realm| {
let args_adapters: Vec<JSValueRef> = args
.into_iter()
.map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
.collect();
let namespace = movable_namespace
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>();
let res = realm.js_function_invoke_by_name(
namespace.as_slice(),
movable_method_name.as_str(),
args_adapters.as_slice(),
);
match res {
Ok(jsvr) => realm.to_js_value_facade(&jsvr),
Err(e) => Err(e),
}
})
}
#[allow(clippy::type_complexity)]
fn js_function_invoke(
&self,
realm_name: Option<&str>,
namespace: &[&str],
method_name: &str,
args: Vec<JsValueFacade>,
) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
let movable_method_name = method_name.to_string();
self.js_loop_realm(realm_name, move |_rt, realm| {
let args_adapters: Vec<JSValueRef> = args
.into_iter()
.map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
.collect();
let namespace = movable_namespace
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>();
let res = realm.js_function_invoke_by_name(
namespace.as_slice(),
movable_method_name.as_str(),
args_adapters.as_slice(),
);
match res {
Ok(jsvr) => realm.to_js_value_facade(&jsvr),
Err(e) => Err(e),
}
})
}
fn js_function_invoke_void(
&self,
realm_name: Option<&str>,
namespace: &[&str],
method_name: &str,
args: Vec<JsValueFacade>,
) {
let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
let movable_method_name = method_name.to_string();
self.js_loop_realm_void(realm_name, move |_rt, realm| {
let args_adapters: Vec<JSValueRef> = args
.into_iter()
.map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
.collect();
let namespace = movable_namespace
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>();
let res = realm
.js_function_invoke_by_name(
namespace.as_slice(),
movable_method_name.as_str(),
args_adapters.as_slice(),
)
.map(|jsvr| realm.to_js_value_facade(&jsvr));
match res {
Ok(_) => {
log::trace!(
"js_function_invoke_void succeeded: {}",
movable_method_name.as_str()
);
}
Err(err) => {
log::trace!(
"js_function_invoke_void failed: {}: {}",
movable_method_name.as_str(),
err
);
}
}
})
}
}
#[cfg(test)]
pub mod tests {
use crate::esvalue::{EsValueConvertible, EsValueFacade};
use crate::facades::QuickJsRuntimeFacade;
use crate::quickjs_utils::{primitives, promises};
use crate::quickjsrealmadapter::QuickJsRealmAdapter;
use crate::valueref::JSValueRef;
use backtrace::Backtrace;
use futures::executor::block_on;
use hirofa_utils::js_utils::modules::{NativeModuleLoader, ScriptModuleLoader};
use hirofa_utils::js_utils::JsError;
use hirofa_utils::js_utils::Script;
use log::debug;
use log::LevelFilter;
use std::panic;
use std::time::Duration;
struct TestNativeModuleLoader {}
struct TestScriptModuleLoader {}
impl NativeModuleLoader<QuickJsRealmAdapter> for TestNativeModuleLoader {
fn has_module(&self, _q_ctx: &QuickJsRealmAdapter, module_name: &str) -> bool {
module_name.starts_with("greco://")
}
fn get_module_export_names(
&self,
_q_ctx: &QuickJsRealmAdapter,
_module_name: &str,
) -> Vec<&str> {
vec!["a", "b", "c"]
}
fn get_module_exports(
&self,
_q_ctx: &QuickJsRealmAdapter,
_module_name: &str,
) -> Vec<(&str, JSValueRef)> {
vec![
("a", primitives::from_i32(1234)),
("b", primitives::from_i32(64834)),
("c", primitives::from_i32(333)),
]
}
}
impl ScriptModuleLoader<QuickJsRealmAdapter> for TestScriptModuleLoader {
fn normalize_path(
&self,
_realm: &QuickJsRealmAdapter,
_ref_path: &str,
path: &str,
) -> Option<String> {
if path.eq("notfound.mes") || path.starts_with("greco://") {
None
} else {
Some(path.to_string())
}
}
fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
if absolute_path.eq("notfound.mes") || absolute_path.starts_with("greco://") {
panic!("tht realy should not happen");
} else if absolute_path.eq("invalid.mes") {
"I am the great cornholio! thou'gh shalt&s not p4arse mie!".to_string()
} else {
"export const foo = 'bar';\nexport const mltpl = function(a, b){return a*b;}; globalThis;".to_string()
}
}
}
#[test]
fn test_rt_drop() {
let rt = init_test_rt();
log::trace!("before drop");
drop(rt);
log::trace!("after before drop");
std::thread::sleep(Duration::from_secs(5));
log::trace!("after sleep");
}
#[test]
pub fn test_stack_size() {
let rt = init_test_rt();
let res = rt.eval_sync(Script::new(
"stack_test.js",
"let f = function(a){let f2 = arguments.callee; if (a < 120) {f2(a + 1);}}; f(1);",
));
match res {
Ok(_) => {}
Err(e) => {
log::error!("fail: {}", e);
panic!("fail: {}", e);
}
}
let res = rt.eval_sync(Script::new(
"stack_test.js",
"let f = function(a){let f2 = arguments.callee; if (a < 1000) {f2(a + 1);}}; f(1);",
));
if res.is_ok() {
panic!("stack should have overflowed");
}
}
pub fn init_test_rt() -> QuickJsRuntimeFacade {
panic::set_hook(Box::new(|panic_info| {
let backtrace = Backtrace::new();
println!(
"thread panic occurred: {}\nbacktrace: {:?}",
panic_info, backtrace
);
log::error!(
"thread panic occurred: {}\nbacktrace: {:?}",
panic_info,
backtrace
);
}));
simple_logging::log_to_file("quickjs_runtime.log", LevelFilter::max())
.expect("could not init logger");
QuickJsRuntimeFacade::builder()
.gc_interval(Duration::from_secs(1))
.max_stack_size(128 * 1024)
.script_module_loader(Box::new(TestScriptModuleLoader {}))
.native_module_loader(Box::new(TestNativeModuleLoader {}))
.build()
}
#[test]
fn test_func() {
let rt = init_test_rt();
let res = rt.set_function(vec!["nl", "my", "utils"], "methodA", |_q_ctx, args| {
if args.len() != 2 || !args.get(0).unwrap().is_i32() || !args.get(1).unwrap().is_i32() {
Err(JsError::new_str(
"i'd really like 2 args of the int32 kind please",
))
} else {
let a = args.get(0).unwrap().get_i32();
let b = args.get(1).unwrap().get_i32();
Ok((a * b).to_es_value_facade())
}
});
match res {
Ok(_) => {}
Err(e) => {
panic!("set_function failed: {}", e);
}
}
let res = rt.eval_sync(Script::new(
"test_func.es",
"(nl.my.utils.methodA(13, 56));",
));
match res {
Ok(val) => {
assert!(val.is_i32());
assert_eq!(val.get_i32(), 13 * 56);
}
Err(e) => {
panic!("test_func.es failed: {}", e);
}
}
}
#[test]
fn test_eval_sync() {
let rt = init_test_rt();
let res = rt.eval_sync(Script::new("test.es", "console.log('foo bar');"));
match res {
Ok(_) => {}
Err(e) => {
panic!("eval failed: {}", e);
}
}
let res = rt
.eval_sync(Script::new("test.es", "(2 * 7);"))
.expect("script failed");
assert_eq!(res.get_i32(), 14);
}
#[test]
fn t1234() {
let rt = init_test_rt();
rt.exe_rt_task_in_event_loop(|q_js_rt| {
let q_ctx = q_js_rt.get_main_context();
let r = q_ctx.eval(Script::new(
"test_async.es",
"let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f();",
)).ok().unwrap();
log::trace!("tag = {}", r.get_tag());
assert!(promises::is_promise_q(q_ctx, &r));
if promises::is_promise_q(q_ctx, &r) {
log::info!("r IS a Promise");
} else {
log::error!("r is NOT a Promise");
}
std::thread::sleep(Duration::from_secs(1));
});
rt.exe_rt_task_in_event_loop(|q_js_rt| {
q_js_rt.run_pending_jobs_if_any();
});
std::thread::sleep(Duration::from_secs(1));
}
#[test]
fn test_eval_await() {
let rt = init_test_rt();
let res = rt.eval_sync(Script::new(
"test_async.es",
"{let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f()};",
));
match res {
Ok(esvf) => {
assert!(esvf.is_promise());
let p_res = esvf.get_promise_result_sync();
if p_res.is_err() {
panic!("{:?}", p_res.err().unwrap());
}
let res = p_res.ok().unwrap();
assert!(res.is_i32());
assert_eq!(res.get_i32(), 12345);
}
Err(e) => {
panic!("eval failed: {}", e);
}
}
}
#[test]
fn test_promise() {
let rt = init_test_rt();
let res = rt.eval_sync(Script::new(
"testp2.es",
"let test_promise_P = (new Promise(function(res, rej) {console.log('before res');res(123);console.log('after res');}).then(function (a) {console.log('prom ressed to ' + a);}).catch(function(x) {console.log('p.ca ex=' + x);}))",
));
match res {
Ok(_) => {}
Err(e) => panic!("p script failed: {}", e),
}
std::thread::sleep(Duration::from_secs(1));
}
#[test]
fn test_module_sync() {
log::info!("> test_module_sync");
let rt = init_test_rt();
debug!("test static import");
let res: Result<EsValueFacade, JsError> = rt.eval_module_sync(Script::new(
"test.es",
"import {foo} from 'test_module.mes';\n console.log('static imp foo = ' + foo);",
));
match res {
Ok(_) => {
log::debug!("static import ok");
}
Err(e) => {
log::error!("static import failed: {}", e);
}
}
debug!("test dynamic import");
let res: Result<EsValueFacade, JsError> = rt.eval_sync(Script::new(
"test_dyn.es",
"console.log('about to load dynamic module');let dyn_p = import('test_module.mes');dyn_p.then(function (some) {console.log('after dyn');console.log('after dyn ' + typeof some);console.log('mltpl 5, 7 = ' + some.mltpl(5, 7));});dyn_p.catch(function (x) {console.log('imp.cat x=' + x);});console.log('dyn done');",
));
match res {
Ok(_) => {
log::debug!("dynamic import ok");
}
Err(e) => {
log::error!("dynamic import failed: {}", e);
}
}
std::thread::sleep(Duration::from_secs(1));
log::info!("< test_module_sync");
}
async fn test_async1() -> i32 {
let rt = init_test_rt();
let a = rt.eval(Script::new("test_async.es", "122 + 1;")).await;
match a {
Ok(a) => a.get_i32(),
Err(e) => panic!("script failed: {}", e),
}
}
#[test]
fn test_async() {
let fut = test_async1();
let res = block_on(fut);
assert_eq!(res, 123);
}
#[test]
fn test_macro() {
let _args = es_args!(1, 2i32, true, "sdf".to_string());
}
}
#[cfg(test)]
pub mod abstraction_tests {
use crate::builder::QuickJsRuntimeBuilder;
use crate::facades::tests::init_test_rt;
use futures::executor::block_on;
use hirofa_utils::js_utils::adapters::JsRealmAdapter;
use hirofa_utils::js_utils::facades::values::JsValueFacade;
use hirofa_utils::js_utils::facades::JsRuntimeFacade;
use hirofa_utils::js_utils::Script;
async fn example<T: JsRuntimeFacade>(rt: &T) -> JsValueFacade {
rt.js_loop_realm(None, |_rt_adapter, realm_adapter| {
let script = Script::new("example.js", "7 + 13");
let value_adapter = realm_adapter.js_eval(script).expect("script failed");
realm_adapter
.to_js_value_facade(&value_adapter)
.expect("conversion failed")
})
.await
}
#[test]
fn test1() {
let rt = QuickJsRuntimeBuilder::new().build();
let val = block_on(example(&rt));
if let JsValueFacade::I32 { val } = val {
assert_eq!(val, 20);
} else {
panic!("not an i32");
}
}
#[tokio::test]
async fn test_serde() {
let json = r#"
{
"a": 1,
"b": true,
"c": {
"d": "q",
"e": [1, 2, 3.3]
}
}
"#;
let value = serde_json::from_str::<serde_json::Value>(&json).expect("json fail");
let input: JsValueFacade = JsValueFacade::SerdeValue { value };
let rt = init_test_rt();
let _ = rt.js_eval(None,Script::new("t.js", r#"
function testSerde(input) {
return "" + input.a + input.b + input.c.d + input.c.e[0] + input.c.e[1] + input.c.e[2];
}
"#)).await.expect("script failed");
let res = rt
.js_function_invoke(None, &[], "testSerde", vec![input])
.await
.expect("func failed");
assert!(res.is_string());
assert_eq!(res.get_str(), "1trueq123.3");
}
}