use std::{cell::RefCell, collections::HashMap};
use anyhow::{Result, anyhow};
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::{
PXS_METHOD_NAME, PixelScript, pxs_Opaque, read_file, utils::CStringSafe, var::{ObjectMethods, pxs_Var}
}
};
#[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) {
self.defined_objects.get_mut().clear();
self.module_exports.get_mut().clear();
unsafe {
if self.context != std::ptr::null_mut() {
quickjs::JS_FreeContext(self.context);
}
if self.rt != std::ptr::null_mut() {
quickjs::JS_FreeRuntime(self.rt);
}
}
}
}
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_borrow(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_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);
let state = get_js_state();
let modules = state.modules.borrow();
let mut import_modules_code = String::new();
for m in modules.iter() {
import_modules_code.push_str(format!("import '{}';", m.0).as_str());
}
run_js(&import_modules_code, "<cleanup>", quickjs::JS_EVAL_TYPE_MODULE as i32);
}
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();
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 mod_obj = run_js(code, "<code_object>", (quickjs::JS_EVAL_FLAG_COMPILE_ONLY | quickjs::JS_EVAL_TYPE_MODULE) as i32);
if mod_obj.is_exception() || mod_obj.is_error() {
return Err(anyhow!("{}", mod_obj.get_error_exception().unwrap()));
}
let res = SmartJSValue::new_owned(unsafe {
quickjs::JS_EvalFunction(mod_obj.context, mod_obj.value)
}, mod_obj.context);
if res.is_exception() || res.is_error() {
return Err(anyhow!("{}", res.get_error_exception().unwrap()));
}
let pxs_val = js_into_pxs(&mod_obj)?;
let global_scope_js_object = pxs_into_js(mod_obj.context, &global_scope)?;
let global_scope_pxs = js_into_pxs(&global_scope_js_object)?;
let result = pxs_Var::new_list();
let list = result.get_list().unwrap();
list.add_item(pxs_val);
list.add_item(global_scope_pxs);
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 code_object_pxs = list.get_item(1).unwrap();
let global_scope = list.get_item(2).unwrap();
let code_object_js = pxs_into_js(state.context, &code_object_pxs)?;
if !code_object_js.is_module() {
return Err(anyhow!("Expected module, found: {}", code_object_js.type_string()));
}
let ns = code_object_js.get_module_namespace();
let pxs_method = ns.get_prop(PXS_METHOD_NAME);
if !pxs_method.is_function() {
return Err(anyhow!("Expected function for __pxs__, found: {}", pxs_method.type_string()));
}
let args = vec![
pxs_into_js(state.context, &global_scope)?,
pxs_into_js(state.context, &local_scope)?
];
let res = pxs_method.call_as_source(&args);
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))
}
}