use crate::{
atom::{self, Atom},
qjs,
value::Constructor,
Array, BigInt, Ctx, Error, FromJs, Function, IntoJs, Object, Result, String, Symbol, Value,
};
use std::{
fmt,
mem::{self, ManuallyDrop},
};
pub unsafe trait Outlive<'js> {
type Target<'to>;
}
macro_rules! outlive_impls {
($($type:ident,)*) => {
$(
unsafe impl<'js> Outlive<'js> for $type<'js> {
type Target<'to> = $type<'to>;
}
)*
};
}
outlive_impls! {
Value,
Symbol,
String,
Object,
Array,
BigInt,
Function,
Constructor,
Atom,
}
macro_rules! impl_outlive{
($($($ty:ident)::+$(<$($g:ident),+>)*),*$(,)?) => {
$(
unsafe impl<'js,$($($g,)*)*> Outlive<'js> for $($ty)::*$(<$($g,)*>)*
where
$($($g: Outlive<'js>,)*)*
{
type Target<'to> = $($ty)::*$(<$($g::Target<'to>,)*>)*;
}
)*
};
}
impl_outlive!(
u8,
u16,
u32,
u64,
usize,
u128,
i8,
i16,
i32,
i64,
isize,
i128,
char,
std::string::String,
Vec<T>,
Box<T>,
Option<T>,
std::result::Result<T,E>,
std::backtrace::Backtrace,
std::cell::Cell<T>,
std::cell::RefCell<T>,
std::cell::UnsafeCell<T>,
std::collections::BTreeMap<K,V>,
std::collections::BTreeSet<K>,
std::collections::BinaryHeap<K>,
std::collections::HashMap<K,V>,
std::collections::HashSet<K>,
std::collections::LinkedList<T>,
std::collections::VecDeque<T>,
std::ffi::CString,
std::ffi::OsString,
std::ops::Range<T>,
std::ops::RangeFrom<T>,
std::ops::RangeFull,
std::ops::RangeInclusive<T>,
std::ops::RangeTo<T>,
std::ops::RangeToInclusive<T>,
std::ops::Bound<T>,
std::ops::ControlFlow<B,C>,
std::process::Child,
std::process::Command,
std::process::ExitCode,
std::process::ExitStatus,
std::process::Output,
std::process::Stdio,
std::path::PathBuf,
std::rc::Rc<T>,
std::sync::Arc<T>,
std::sync::Mutex<T>,
std::sync::RwLock<T>,
atom::PredefinedAtom,
);
#[derive(Eq, PartialEq, Hash)]
pub struct Persistent<T> {
pub(crate) rt: *mut qjs::JSRuntime,
pub(crate) value: T,
}
impl<T: Clone> Clone for Persistent<T> {
fn clone(&self) -> Self {
Persistent {
rt: self.rt,
value: self.value.clone(),
}
}
}
impl<T> fmt::Debug for Persistent<T>
where
T: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Persistent")
.field("rt", &self.rt)
.field("value", &self.value)
.finish()
}
}
impl<T> Persistent<T> {
fn new_raw(rt: *mut qjs::JSRuntime, value: T) -> Self {
Self { rt, value }
}
unsafe fn outlive_transmute<'from, 'to, U>(t: U) -> U::Target<'to>
where
U: Outlive<'from>,
{
assert_eq!(mem::size_of::<U>(), mem::size_of::<U::Target<'static>>());
assert_eq!(mem::align_of::<U>(), mem::align_of::<U::Target<'static>>());
union Transmute<A, B> {
a: ManuallyDrop<A>,
b: ManuallyDrop<B>,
}
let data = Transmute::<U, U::Target<'to>> {
a: ManuallyDrop::new(t),
};
unsafe { ManuallyDrop::into_inner(data.b) }
}
pub fn save<'js>(ctx: &Ctx<'js>, val: T) -> Persistent<T::Target<'static>>
where
T: Outlive<'js>,
{
let outlived: T::Target<'static> =
unsafe { Self::outlive_transmute::<'js, 'static, T>(val) };
let ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
Persistent {
rt: ptr,
value: outlived,
}
}
pub fn restore<'js>(self, ctx: &Ctx<'js>) -> Result<T::Target<'js>>
where
T: Outlive<'static>,
{
let ctx_runtime_ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
if self.rt != ctx_runtime_ptr {
return Err(Error::UnrelatedRuntime);
}
Ok(unsafe { Self::outlive_transmute::<'static, 'js, T>(self.value) })
}
}
impl<'js, T, R> FromJs<'js> for Persistent<R>
where
R: Outlive<'static, Target<'js> = T>,
T: Outlive<'js, Target<'static> = R> + FromJs<'js>,
{
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Persistent<R>> {
let value = T::from_js(ctx, value)?;
Ok(Persistent::save(ctx, value))
}
}
impl<'js, T> IntoJs<'js> for Persistent<T>
where
T: Outlive<'static>,
T::Target<'js>: IntoJs<'js>,
{
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
self.restore(ctx)?.into_js(ctx)
}
}
#[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 different_context() {
let rt1 = Runtime::new().unwrap();
let ctx1 = Context::full(&rt1).unwrap();
let ctx2 = Context::full(&rt1).unwrap();
let persistent_v = ctx1.with(|ctx| {
let v: Object = ctx.eval("({ a: 1 })").unwrap();
Persistent::save(&ctx, v)
});
std::mem::drop(ctx1);
ctx2.with(|ctx| {
let obj: Object = persistent_v.clone().restore(&ctx).unwrap();
assert_eq!(obj.get::<_, i32>("a").unwrap(), 1);
});
}
#[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());
});
}
}