use crate::{
qjs, Array, BigInt, Ctx, Error, FromJs, Function, IntoJs, Object, Result, String, Symbol, Value,
};
use std::{
cell::Cell,
cmp::PartialEq,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
mem,
};
pub trait Outlive<'t> {
type Target;
}
macro_rules! outlive_impls {
($($type:ident,)*) => {
$(
impl<'js, 't> Outlive<'t> for $type<'js> {
type Target = $type<'t>;
}
)*
};
}
outlive_impls! {
Value,
Function,
Symbol,
String,
Object,
Array,
BigInt,
}
pub struct Persistent<T> {
pub(crate) rt: *mut qjs::JSRuntime,
pub(crate) value: Cell<qjs::JSValue>,
marker: PhantomData<T>,
}
impl<T> Clone for Persistent<T> {
fn clone(&self) -> Self {
let value = unsafe { qjs::JS_DupValue(self.value.get()) };
Self::new_raw(self.rt, value)
}
}
impl<T> fmt::Debug for Persistent<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Persistent")
.field("rt", &self.rt)
.field("ptr", &unsafe { qjs::JS_VALUE_GET_PTR(self.value.get()) })
.finish()
}
}
impl<T> Drop for Persistent<T> {
fn drop(&mut self) {
unsafe { qjs::JS_FreeValueRT(self.rt, self.value.get()) };
}
}
impl<T> Persistent<T> {
fn new_raw(rt: *mut qjs::JSRuntime, value: qjs::JSValue) -> Self {
Self {
rt,
value: Cell::new(value),
marker: PhantomData,
}
}
#[cfg(feature = "classes")]
pub(crate) fn mark_raw(&self, mark_func: qjs::JS_MarkFunc) {
let value = self.value.get();
if unsafe { qjs::JS_VALUE_HAS_REF_COUNT(value) } {
unsafe { qjs::JS_MarkValue(self.rt, value, mark_func) };
if 0 == unsafe { qjs::JS_ValueRefCount(value) } {
self.value.set(qjs::JS_UNDEFINED);
}
}
}
pub fn save<'js>(ctx: Ctx<'js>, val: T) -> Persistent<T::Target>
where
T: AsRef<Value<'js>> + Outlive<'static>,
{
let value = val.as_ref().value;
mem::forget(val);
let rt = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
Persistent::new_raw(rt, value)
}
pub fn restore<'js>(self, ctx: Ctx<'js>) -> Result<T::Target>
where
T: Outlive<'js>,
T::Target: FromJs<'js>,
{
let ctx_runtime_ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
if self.rt != ctx_runtime_ptr {
return Err(Error::UnrelatedRuntime);
}
let value = unsafe { Value::from_js_value(ctx, self.value.get()) };
mem::forget(self);
T::Target::from_js(ctx, value)
}
fn ptr(&self) -> *mut qjs::c_void {
unsafe { qjs::JS_VALUE_GET_PTR(self.value.get()) }
}
fn tag(&self) -> qjs::c_int {
unsafe { qjs::JS_VALUE_GET_TAG(self.value.get()) }
}
}
impl<'js, T> FromJs<'js> for Persistent<T>
where
T: Outlive<'js>,
T::Target: FromJs<'js> + IntoJs<'js>,
{
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Persistent<T>> {
let value = T::Target::from_js(ctx, value)?;
let value = value.into_js(ctx)?;
let value = value.into_js_value();
let rt = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
Ok(Self::new_raw(rt, value))
}
}
impl<'js, 't, T> IntoJs<'js> for Persistent<T>
where
T: Outlive<'t>,
{
fn into_js(self, ctx: Ctx<'js>) -> Result<Value<'js>> {
let value = unsafe { Value::from_js_value(ctx, self.value.get()) };
mem::forget(self);
value.into_js(ctx)
}
}
#[cfg(feature = "parallel")]
unsafe impl<T> Send for Persistent<T> {}
impl<T> Hash for Persistent<T> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.ptr().hash(state);
self.tag().hash(state);
}
}
impl<T, S> PartialEq<Persistent<S>> for Persistent<T> {
fn eq(&self, other: &Persistent<S>) -> bool {
(self.tag() == other.tag()) && (self.ptr() == other.ptr())
}
}
impl<T> Eq for Persistent<T> {}
#[cfg(test)]
mod test {
use crate::*;
#[test]
#[should_panic(expected = "UnrelatedRuntime")]
fn different_runtime() {
let rt1 = Runtime::new().unwrap();
let ctx = Context::full(&rt1).unwrap();
let persistent_v = ctx.with(|ctx| {
let v: Value = ctx.eval("1").unwrap();
Persistent::save(ctx, v)
});
let rt2 = Runtime::new().unwrap();
let ctx = Context::full(&rt2).unwrap();
ctx.with(|ctx| {
let _ = persistent_v.clone().restore(ctx).unwrap();
});
}
#[test]
fn persistent_function() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
let func = ctx.with(|ctx| {
let func: Function = ctx.eval("a => a + 1").unwrap();
Persistent::save(ctx, func)
});
let res: i32 = ctx.with(|ctx| {
let func = func.clone().restore(ctx).unwrap();
func.call((2,)).unwrap()
});
assert_eq!(res, 3);
let ctx2 = Context::full(&rt).unwrap();
let res: i32 = ctx2.with(|ctx| {
let func = func.restore(ctx).unwrap();
func.call((0,)).unwrap()
});
assert_eq!(res, 1);
}
#[test]
fn persistent_value() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
let persistent_v = ctx.with(|ctx| {
let v: Value = ctx.eval("1").unwrap();
Persistent::save(ctx, v)
});
ctx.with(|ctx| {
let v = persistent_v.clone().restore(ctx).unwrap();
ctx.globals().set("v", v).unwrap();
let eq: Value = ctx.eval("v == 1").unwrap();
assert!(eq.as_bool().unwrap());
});
}
}