use crate::{JSCContext, JSCValue, jsc};
use rong_core::{JSClass, JSClassExt, JSContextImpl, JSTypeOf, JSValueImpl};
use std::collections::HashMap;
use std::ffi::{CString, c_char};
use std::ptr;
use std::sync::{LazyLock, RwLock};
static CLASS: LazyLock<RwLock<HashMap<usize, usize>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn get_classref_by_constructor(constructor: JSCValue) -> jsc::JSClassRef {
let constructor_ptr = constructor.as_value() as usize;
if let Ok(map) = CLASS.read()
&& let Some(&class_ref) = map.get(&constructor_ptr)
{
return class_ref as jsc::JSClassRef;
}
ptr::null_mut()
}
unsafe extern "C" fn generic_constructor<JC>(
ctx: jsc::JSContextRef,
constructor: jsc::JSObjectRef,
argument_count: usize,
arguments: *const jsc::JSValueRef,
exception: *mut jsc::JSValueRef,
) -> jsc::JSObjectRef
where
JC: JSClass<JSCValue>,
{
let raw: *mut jsc::OpaqueJSContext = ctx as _;
let this = JSCValue::from_borrowed_obj(raw, constructor);
let args: Vec<_> = (0..argument_count)
.map(move |i| unsafe { JSCValue::from_borrowed_raw(raw, *arguments.add(i)) })
.collect();
let ctx = JSCContext::from_borrowed_raw(raw);
let value = <JC as JSClassExt<JSCValue>>::constructor(&ctx, this, args);
if value.is_exception() {
if !exception.is_null() {
unsafe {
*exception = value.into_raw_value();
}
}
unsafe {
return jsc::JSValueMakeUndefined(ctx.to_raw()) as _;
}
}
value.into_raw_value() as jsc::JSObjectRef
}
unsafe extern "C" fn finalizer<JC>(object: jsc::JSObjectRef)
where
JC: JSClass<JSCValue>,
{
let classid = unsafe { jsc::JSObjectGetPrivate(object) } as usize;
if classid & 0x1 == 1 {
let class_ref = classid & !0x1;
unsafe {
jsc::JSClassRelease(class_ref as _);
}
return;
}
let ctx: *mut jsc::OpaqueJSContext = std::ptr::null_mut();
let value = JSCValue::from_borrowed_obj(ctx, object);
<JC as JSClassExt<JSCValue>>::free(value);
}
unsafe extern "C" fn call_as_function<JC>(
ctx: jsc::JSContextRef,
function: jsc::JSObjectRef,
this_object: jsc::JSObjectRef,
argument_count: usize,
arguments: *const jsc::JSValueRef,
exception: *mut jsc::JSValueRef,
) -> jsc::JSValueRef
where
JC: JSClass<JSCValue>,
{
let ctx = JSCContext::from_borrowed_raw(ctx as _);
let function = JSCValue::from_borrowed_obj(ctx.to_raw(), function);
let this = JSCValue::from_borrowed_obj(ctx.to_raw(), this_object);
let args: Vec<JSCValue> = if !arguments.is_null() {
(0..argument_count)
.map(|i| unsafe { JSCValue::from_borrowed_raw(ctx.to_raw(), *arguments.add(i)) })
.collect()
} else {
vec![]
};
let value = <JC as JSClassExt<JSCValue>>::call(&ctx, function, this, args);
if value.is_exception() {
if !exception.is_null() {
unsafe {
*exception = value.into_raw_value();
}
}
unsafe {
return jsc::JSValueMakeUndefined(ctx.to_raw());
}
}
value.into_raw_value()
}
unsafe extern "C" fn has_instance(
ctx: jsc::JSContextRef,
_constructor: jsc::JSObjectRef,
possible_instance: jsc::JSValueRef,
_exception: *mut jsc::JSValueRef,
) -> bool {
if unsafe { jsc::JSValueIsObject(ctx, possible_instance) } {
let instance =
unsafe { jsc::JSValueToObject(ctx, possible_instance, std::ptr::null_mut()) };
let private = unsafe { jsc::JSObjectGetPrivate(instance) };
if !private.is_null() {
return true;
}
}
false
}
pub(crate) fn register_class_internal<JC>(ctx: &JSCContext, class_name: &str) -> JSCValue
where
JC: JSClass<JSCValue>,
{
let class_name_cstr = CString::new(class_name).unwrap();
let ctx = ctx.to_raw();
unsafe {
let class_def = jsc::JSClassDefinition {
version: 0,
attributes: jsc::kJSClassAttributeNone,
className: class_name_cstr.as_ptr(),
parentClass: ptr::null_mut(),
staticValues: ptr::null(),
staticFunctions: ptr::null(),
initialize: None,
finalize: Some(finalizer::<JC>),
hasProperty: None,
getProperty: None,
setProperty: None,
deleteProperty: None,
getPropertyNames: None,
callAsFunction: Some(call_as_function::<JC>),
callAsConstructor: None,
hasInstance: Some(has_instance),
convertToType: None,
};
let js_export = jsc::JSClassCreate(&class_def);
let constructor =
jsc::JSObjectMakeConstructor(ctx, js_export, Some(generic_constructor::<JC>));
let name_key = jsc::JSStringCreateWithUTF8CString(c"name".as_ptr());
let class_name = jsc::JSStringCreateWithUTF8CString(class_name_cstr.as_ptr());
let name_value = jsc::JSValueMakeString(ctx, class_name);
jsc::JSObjectSetProperty(
ctx,
constructor,
name_key,
name_value,
jsc::kJSPropertyAttributeReadOnly | jsc::kJSPropertyAttributeDontEnum,
ptr::null_mut(),
);
jsc::JSStringRelease(name_key);
jsc::JSStringRelease(class_name);
let function = get_constructor(ctx, c"Function".as_ptr());
let constructor_key = jsc::JSStringCreateWithUTF8CString(c"constructor".as_ptr());
jsc::JSObjectSetProperty(
ctx,
constructor,
constructor_key,
function,
jsc::kJSPropertyAttributeDontEnum,
ptr::null_mut(),
);
let prototype_key = jsc::JSStringCreateWithUTF8CString(c"prototype".as_ptr());
let prototype = jsc::JSObjectGetProperty(ctx, constructor, prototype_key, ptr::null_mut());
let prototype = jsc::JSValueToObject(ctx, prototype, ptr::null_mut());
jsc::JSObjectSetProperty(
ctx,
prototype,
constructor_key,
constructor,
jsc::kJSPropertyAttributeDontEnum,
ptr::null_mut(),
);
jsc::JSStringRelease(prototype_key);
jsc::JSStringRelease(constructor_key);
CLASS
.write()
.unwrap()
.insert(constructor as usize, js_export as usize);
JSCValue::from_owned_obj(ctx, constructor)
}
}
pub(crate) fn get_constructor(
ctx: *mut jsc::OpaqueJSContext,
name: *const c_char,
) -> jsc::JSObjectRef {
unsafe {
let global = jsc::JSContextGetGlobalObject(ctx);
let js_name = jsc::JSStringCreateWithUTF8CString(name);
let value = jsc::JSObjectGetProperty(ctx, global, js_name, ptr::null_mut());
jsc::JSStringRelease(js_name);
jsc::JSValueToObject(ctx, value, ptr::null_mut())
}
}