rquickjs_core/
persistent.rs1use crate::{qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Result, Value};
2
3use std::{
4 fmt,
5 mem::{self, ManuallyDrop},
6};
7
8#[derive(Eq, PartialEq, Hash)]
37pub struct Persistent<T> {
38 pub(crate) rt: *mut qjs::JSRuntime,
39 pub(crate) value: T,
40}
41
42impl<T: Clone> Clone for Persistent<T> {
43 fn clone(&self) -> Self {
44 Persistent {
45 rt: self.rt,
46 value: self.value.clone(),
47 }
48 }
49}
50
51impl<T> fmt::Debug for Persistent<T>
52where
53 T: fmt::Debug,
54{
55 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
56 fmt.debug_struct("Persistent")
57 .field("rt", &self.rt)
58 .field("value", &self.value)
59 .finish()
60 }
61}
62
63impl<T> Persistent<T> {
64 unsafe fn outlive_transmute<'from, 'to, U>(t: U) -> U::Changed<'to>
65 where
66 U: JsLifetime<'from>,
67 {
68 assert_eq!(mem::size_of::<U>(), mem::size_of::<U::Changed<'static>>());
73 assert_eq!(mem::align_of::<U>(), mem::align_of::<U::Changed<'static>>());
74
75 union Transmute<A, B> {
78 a: ManuallyDrop<A>,
79 b: ManuallyDrop<B>,
80 }
81 let data = Transmute::<U, U::Changed<'to>> {
82 a: ManuallyDrop::new(t),
83 };
84 unsafe { ManuallyDrop::into_inner(data.b) }
85 }
86
87 pub fn save<'js>(ctx: &Ctx<'js>, val: T) -> Persistent<T::Changed<'static>>
89 where
90 T: JsLifetime<'js>,
91 {
92 let outlived: T::Changed<'static> =
93 unsafe { Self::outlive_transmute::<'js, 'static, T>(val) };
94 let ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
95 Persistent {
96 rt: ptr,
97 value: outlived,
98 }
99 }
100
101 pub fn restore<'js>(self, ctx: &Ctx<'js>) -> Result<T::Changed<'js>>
103 where
104 T: JsLifetime<'static>,
105 {
106 let ctx_runtime_ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
107 if self.rt != ctx_runtime_ptr {
108 return Err(Error::UnrelatedRuntime);
109 }
110 Ok(unsafe { Self::outlive_transmute::<'static, 'js, T>(self.value) })
111 }
112}
113
114impl<'js, T, R> FromJs<'js> for Persistent<R>
115where
116 R: JsLifetime<'static, Changed<'js> = T>,
117 T: JsLifetime<'js, Changed<'static> = R> + FromJs<'js>,
118{
119 fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Persistent<R>> {
120 let value = T::from_js(ctx, value)?;
121 Ok(Persistent::save(ctx, value))
122 }
123}
124
125impl<'js, T> IntoJs<'js> for Persistent<T>
126where
127 T: JsLifetime<'static>,
128 T::Changed<'js>: IntoJs<'js>,
129{
130 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
131 self.restore(ctx)?.into_js(ctx)
132 }
133}
134
135#[cfg(test)]
136mod test {
137 use crate::*;
138
139 #[test]
140 #[should_panic(expected = "UnrelatedRuntime")]
141 fn different_runtime() {
142 let rt1 = Runtime::new().unwrap();
143 let ctx = Context::full(&rt1).unwrap();
144
145 let persistent_v = ctx.with(|ctx| {
146 let v: Value = ctx.eval("1").unwrap();
147 Persistent::save(&ctx, v)
148 });
149
150 let rt2 = Runtime::new().unwrap();
151 let ctx = Context::full(&rt2).unwrap();
152 ctx.with(|ctx| {
153 let _ = persistent_v.clone().restore(&ctx).unwrap();
154 });
155 }
156
157 #[test]
158 fn different_context() {
159 let rt1 = Runtime::new().unwrap();
160 let ctx1 = Context::full(&rt1).unwrap();
161 let ctx2 = Context::full(&rt1).unwrap();
162
163 let persistent_v = ctx1.with(|ctx| {
164 let v: Object = ctx.eval("({ a: 1 })").unwrap();
165 Persistent::save(&ctx, v)
166 });
167
168 std::mem::drop(ctx1);
169
170 ctx2.with(|ctx| {
171 let obj: Object = persistent_v.clone().restore(&ctx).unwrap();
172 assert_eq!(obj.get::<_, i32>("a").unwrap(), 1);
173 });
174 }
175
176 #[test]
177 fn persistent_function() {
178 let rt = Runtime::new().unwrap();
179 let ctx = Context::full(&rt).unwrap();
180
181 let func = ctx.with(|ctx| {
182 let func: Function = ctx.eval("a => a + 1").unwrap();
183 Persistent::save(&ctx, func)
184 });
185
186 let res: i32 = ctx.with(|ctx| {
187 let func = func.clone().restore(&ctx).unwrap();
188 func.call((2,)).unwrap()
189 });
190 assert_eq!(res, 3);
191
192 let ctx2 = Context::full(&rt).unwrap();
193 let res: i32 = ctx2.with(|ctx| {
194 let func = func.restore(&ctx).unwrap();
195 func.call((0,)).unwrap()
196 });
197 assert_eq!(res, 1);
198 }
199
200 #[test]
201 fn persistent_value() {
202 let rt = Runtime::new().unwrap();
203 let ctx = Context::full(&rt).unwrap();
204
205 let persistent_v = ctx.with(|ctx| {
206 let v: Value = ctx.eval("1").unwrap();
207 Persistent::save(&ctx, v)
208 });
209
210 ctx.with(|ctx| {
211 let v = persistent_v.clone().restore(&ctx).unwrap();
212 ctx.globals().set("v", v).unwrap();
213 let eq: Value = ctx.eval("v == 1").unwrap();
214 assert!(eq.as_bool().unwrap());
215 });
216 }
217}