use std::{cell::RefCell, collections::HashMap};
use anyhow::Result;
use parking_lot::{ReentrantMutex, ReentrantMutexGuard};
use crate::{
borrow_string, js::{
func::create_callback, module::add_local_module, utils::SmartJSValue, var::{js_into_pxs, pxs_into_js}
}, pxs_debug, shared::{
PixelScript, pxs_Opaque, read_file, utils::CStringSafe, var::{ObjectMethods, pxs_Var, pxs_VarMap}
}
};
#[allow(unused)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
#[allow(dead_code)]
pub(self) mod quickjs {
include!(concat!(env!("OUT_DIR"), "/quickjsng_bindings.rs"));
}
mod func;
mod module;
mod object;
mod var;
mod utils;
pub(self) struct JSModuleMethod {
pub name: String,
pub value: SmartJSValue
}
struct State {
rt: *mut quickjs::JSRuntime,
context: *mut quickjs::JSContext,
defined_objects: RefCell<HashMap<String, SmartJSValue>>,
module_exports: RefCell<HashMap<String, Vec<JSModuleMethod>>>,
modules: RefCell<HashMap<String, *mut quickjs::JSModuleDef>>,
}
impl Drop for State {
fn drop(&mut self) {
unsafe {
if self.context != std::ptr::null_mut() {
quickjs::JS_FreeContext(self.context);
}
}
}
}
thread_local! {
static JSTATE: ReentrantMutex<State> = ReentrantMutex::new(unsafe{init_state()});
}
unsafe extern "C" fn js_module_loader(context: *mut quickjs::JSContext, module_name: *const std::ffi::c_char, _opaque: pxs_Opaque) -> *mut quickjs::JSModuleDef {
let state = get_js_state();
let modules = state.modules.borrow();
unsafe {
let name = borrow_string!(module_name);
if let Some(module) = modules.get(name) {
return *module;
}
let contents = read_file(name);
if contents.len() == 0 {
return std::ptr::null_mut();
}
let mut cstrsafe = CStringSafe::new();
let res = quickjs::JS_Eval(context, cstrsafe.new_string(&contents), contents.len(), module_name, (quickjs::JS_EVAL_TYPE_MODULE | quickjs::JS_EVAL_FLAG_COMPILE_ONLY) as i32);
let smart_res = SmartJSValue::new_owned(res, context);
if smart_res.is_exception() || smart_res.is_error() {
pxs_debug!("Error compiling module");
return std::ptr::null_mut();
}
let val_int = smart_res.value.u.ptr as isize;
let m = ((val_int & !15) as *mut std::ffi::c_void).cast::<quickjs::JSModuleDef>();
m
}
}
unsafe fn init_state() -> State {
unsafe {
let rt = quickjs::JS_NewRuntime();
let ctx = quickjs::JS_NewContext(rt);
quickjs::JS_SetModuleLoaderFunc(rt, None, Some(js_module_loader), std::ptr::null_mut());
let pxs_json = add_local_module(ctx, include_str!("../../core/js/pxs_json.js"), "pxs_json");
let mut modules = HashMap::new();
modules.insert("pxs_json".to_string(), pxs_json);
State {
rt,
context: ctx,
defined_objects: RefCell::new(HashMap::new()),
module_exports: RefCell::new(HashMap::new()),
modules: RefCell::new(modules),
}
}
}
fn get_js_state() -> ReentrantMutexGuard<'static, State> {
JSTATE.with(|mutex| {
let guard = mutex.lock();
unsafe { std::mem::transmute(guard) }
})
}
fn run_js(code: &str, file_name: &str, eval_type: i32) -> SmartJSValue {
let mut cstrsafe = CStringSafe::new();
let state = get_js_state();
unsafe {
let val = quickjs::JS_Eval(state.context, cstrsafe.new_string(code), code.len(), cstrsafe.new_string(file_name), eval_type);
let exception = SmartJSValue::current_exception(state.context);
if exception.is_undefined() {
let smart = SmartJSValue::new_owned(val, state.context);
if smart.is_promise() {
smart.await_value()
} else {
smart
}
} else {
exception
}
}
}
fn get_js_name(name: &str) -> SmartJSValue {
run_js(name, "<get_js_name>", quickjs::JS_EVAL_TYPE_GLOBAL as i32)
}
fn add_map_to_global_this(map: &pxs_VarMap, global_this: &SmartJSValue) -> Result<()> {
for key in map.keys() {
let js_key = pxs_into_js(global_this.context, key)?;
let mut js_val = pxs_into_js(global_this.context, map.get_item(key).unwrap())?;
global_this.set_prop_value(&js_key, &mut js_val);
}
Ok(())
}
fn remove_map_from_global_this(map: &pxs_VarMap, global_this: &SmartJSValue) -> Result<()> {
for key in map.keys() {
let js_key = pxs_into_js(global_this.context, key)?;
global_this.del_prop(&js_key);
}
Ok(())
}
fn add_main_js() {
run_js(include_str!("../../core/js/main.js"), "main.js", quickjs::JS_EVAL_TYPE_MODULE as i32);
}
pub struct JSScripting;
impl PixelScript for JSScripting {
fn start() {
let _state = get_js_state();
add_main_js();
}
fn stop() {
Self::clear_state(false);
}
fn add_module(source: std::sync::Arc<crate::shared::module::pxs_Module>) {
let state = get_js_state();
module::add_module(state.context, &source);
}
fn execute(code: &str, file_name: &str) -> anyhow::Result<crate::shared::var::pxs_Var> {
let res = run_js(code, file_name, (quickjs::JS_EVAL_TYPE_MODULE | quickjs::JS_EVAL_FLAG_ASYNC) as i32);
let pxs_res = js_into_pxs(&res);
if let Err(err) = pxs_res {
Ok(pxs_Var::new_exception(err.to_string()))
} else {
let val = pxs_res.unwrap();
if val.is_exception() {
Ok(val)
} else {
Ok(pxs_Var::new_null())
}
}
}
fn eval(code: &str) -> anyhow::Result<crate::shared::var::pxs_Var> {
let res = run_js(code, "<eval>", (quickjs::JS_EVAL_TYPE_GLOBAL | quickjs::JS_EVAL_FLAG_ASYNC) as i32);
js_into_pxs(&res)
}
fn start_thread() {
}
fn stop_thread() {
}
fn clear_state(call_gc: bool) {
let state = get_js_state();
state.defined_objects.borrow_mut().clear();
state.module_exports.borrow_mut().clear();
state.modules.borrow_mut().clear();
if call_gc {
unsafe {
quickjs::JS_RunGC(state.rt);
}
}
}
fn compile(
code: &str,
global_scope: crate::shared::var::pxs_Var,
) -> anyhow::Result<crate::shared::var::pxs_Var> {
let result = pxs_Var::new_list();
let list = result.get_list().unwrap();
list.add_item(pxs_Var::new_string(code.to_string()));
list.add_item(global_scope);
Ok(result)
}
fn exec_object(
code: crate::shared::var::pxs_Var,
local_scope: crate::shared::var::pxs_Var,
) -> anyhow::Result<crate::shared::var::pxs_Var> {
let state = get_js_state();
let list = code.get_list().unwrap();
let global_this = SmartJSValue::globalThis(state.context);
let global_scope = list.get_item(2).unwrap();
if !global_scope.is_null() {
add_map_to_global_this(global_scope.get_map().unwrap(), &global_this)?;
}
if !local_scope.is_null() {
add_map_to_global_this(local_scope.get_map().unwrap(), &global_this)?;
}
let res = run_js(&list.get_item(1).unwrap().get_string().unwrap(), "<code_obj>", (quickjs::JS_EVAL_TYPE_MODULE | quickjs::JS_EVAL_FLAG_ASYNC) as i32);
remove_map_from_global_this(global_scope.get_map().unwrap(), &global_this)?;
if !local_scope.is_null() {
remove_map_from_global_this(local_scope.get_map().unwrap(), &global_this)?;
}
if res.is_exception() {
Ok(pxs_Var::new_exception(res.get_error_exception().unwrap()))
} else {
js_into_pxs(&res)
}
}
}
impl ObjectMethods for JSScripting {
fn object_call(
var: &crate::shared::var::pxs_Var,
method: &str,
args: &mut crate::shared::var::pxs_VarList,
) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
let state = get_js_state();
let js_var = pxs_into_js(state.context, var)?;
let mut argv = vec![];
for a in args.vars.iter() {
argv.push(pxs_into_js(state.context, a)?);
}
let res = js_var.call(method, &argv);
js_into_pxs(&res)
}
fn call_method(
method: &str,
args: &mut crate::shared::var::pxs_VarList,
) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
let state = get_js_state();
let mut argv = vec![];
for arg in args.vars.iter() {
argv.push(pxs_into_js(state.context, arg)?);
}
let cbk = get_js_name(method);
if !cbk.is_function() {
Ok(pxs_Var::new_exception(format!("{method} is not a Function")))
} else {
let res = cbk.call_as_source(&argv);
js_into_pxs(&res)
}
}
fn var_call(
method: &crate::shared::var::pxs_Var,
args: &mut crate::shared::var::pxs_VarList,
) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
let state = get_js_state();
let smart_val = pxs_into_js(state.context, method)?;
let mut argv = vec![];
for arg in args.vars.iter() {
argv.push(pxs_into_js(state.context, arg)?);
}
let res = smart_val.call_as_source(&argv);
js_into_pxs(&res)
}
fn get(
var: &crate::shared::var::pxs_Var,
key: &str,
) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
let state = get_js_state();
let this = pxs_into_js(state.context, var)?;
let res = this.get_prop(key);
js_into_pxs(&res)
}
fn set(
var: &crate::shared::var::pxs_Var,
key: &str,
value: &crate::shared::var::pxs_Var,
) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
let state = get_js_state();
let this = pxs_into_js(state.context, var)?;
let mut value = pxs_into_js(state.context, value)?;
this.set_prop(key, &mut value);
Ok(pxs_Var::new_bool(true))
}
fn get_from_name(name: &str) -> Result<crate::shared::var::pxs_Var, anyhow::Error> {
js_into_pxs(&get_js_name(name))
}
}