rquickjs_core/runtime/
userdata.rs

1use core::fmt;
2use std::{
3    any::{Any, TypeId},
4    cell::{Cell, UnsafeCell},
5    collections::HashMap,
6    hash::{BuildHasherDefault, Hasher},
7    mem::ManuallyDrop,
8    ops::Deref,
9};
10
11use crate::JsLifetime;
12
13unsafe fn to_static<'js, T>(this: T) -> T::Changed<'static>
14where
15    T: JsLifetime<'js> + Sized,
16{
17    assert_eq!(
18        std::mem::size_of::<T>(),
19        std::mem::size_of::<T::Changed<'static>>(),
20        "Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
21    );
22    assert_eq!(
23        std::mem::align_of::<T>(),
24        std::mem::align_of::<T::Changed<'static>>(),
25        "Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
26    );
27
28    // a super unsafe way to cast between types, This is necessary because normal transmute will
29    // complain that Self and Self::Static are not related so might not have the same size.
30    union Trans<A, B> {
31        from: ManuallyDrop<A>,
32        to: ManuallyDrop<B>,
33    }
34
35    ManuallyDrop::into_inner(
36        (Trans {
37            from: ManuallyDrop::new(this),
38        })
39        .to,
40    )
41}
42
43unsafe fn from_static_box<'js, T>(this: Box<T::Changed<'static>>) -> Box<T>
44where
45    T: JsLifetime<'js> + Sized,
46{
47    assert_eq!(
48        std::mem::size_of::<T>(),
49        std::mem::size_of::<T::Changed<'static>>(),
50        "Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
51    );
52    assert_eq!(
53        std::mem::align_of::<T>(),
54        std::mem::align_of::<T::Changed<'static>>(),
55        "Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
56    );
57
58    Box::from_raw(Box::into_raw(this) as *mut T)
59}
60
61unsafe fn from_static_ref<'a, 'js, T>(this: &'a T::Changed<'static>) -> &'a T
62where
63    T: JsLifetime<'js> + Sized,
64{
65    std::mem::transmute(this)
66}
67
68pub struct UserDataError<T>(pub T);
69
70impl<T> fmt::Display for UserDataError<T> {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(
73            f,
74            "tried to mutate the user data store while it was being referenced"
75        )
76    }
77}
78
79impl<T> fmt::Debug for UserDataError<T> {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        fmt::Display::fmt(self, f)
82    }
83}
84
85#[derive(Default)]
86struct IdHasher(u64);
87
88impl Hasher for IdHasher {
89    fn write(&mut self, _: &[u8]) {
90        unreachable!("TypeId calls write_u64");
91    }
92
93    fn write_u64(&mut self, id: u64) {
94        self.0 = id;
95    }
96
97    fn finish(&self) -> u64 {
98        self.0
99    }
100}
101
102/// Typeid hashmap taken from axum.
103#[derive(Default)]
104pub(crate) struct UserDataMap {
105    map: UnsafeCell<HashMap<TypeId, Box<dyn Any>, BuildHasherDefault<IdHasher>>>,
106    count: Cell<usize>,
107}
108
109impl UserDataMap {
110    pub fn insert<'js, U>(&self, data: U) -> Result<Option<Box<U>>, UserDataError<U>>
111    where
112        U: JsLifetime<'js>,
113        U::Changed<'static>: Any,
114    {
115        if self.count.get() > 0 {
116            return Err(UserDataError(data));
117        }
118        let user_static = unsafe { to_static(data) };
119        let id = TypeId::of::<U::Changed<'static>>();
120        let r = unsafe { (*self.map.get()).insert(id, Box::new(user_static)) }.map(|x| {
121            let r = x
122                .downcast()
123                .expect("type confusion! userdata not stored under the right type id");
124            unsafe { from_static_box(r) }
125        });
126        Ok(r)
127    }
128
129    pub fn remove<'js, U>(&self) -> Result<Option<Box<U>>, UserDataError<()>>
130    where
131        U: JsLifetime<'js>,
132        U::Changed<'static>: Any,
133    {
134        if self.count.get() > 0 {
135            return Err(UserDataError(()));
136        }
137        let id = TypeId::of::<U::Changed<'static>>();
138        let r = unsafe { (*self.map.get()).remove(&id) }.map(|x| {
139            let r = x
140                .downcast()
141                .expect("type confusion! userdata not stored under the right type id");
142            unsafe { from_static_box(r) }
143        });
144        Ok(r)
145    }
146
147    pub fn get<'js, U>(&self) -> Option<UserDataGuard<U>>
148    where
149        U: JsLifetime<'js>,
150        U::Changed<'static>: Any,
151    {
152        let id = TypeId::of::<U::Changed<'static>>();
153        unsafe { (*self.map.get()).get(&id) }.map(|x| {
154            self.count.set(self.count.get() + 1);
155            let u = x
156                .downcast_ref()
157                .expect("type confusion! userdata not stored under the right type id");
158
159            let r = unsafe { from_static_ref(u) };
160            UserDataGuard { map: self, r }
161        })
162    }
163
164    pub fn clear(&mut self) {
165        self.map.get_mut().clear()
166    }
167}
168
169/// Guard for user data to avoid inserting new userdata while exisiting userdata is being
170/// referenced.
171pub struct UserDataGuard<'a, U> {
172    map: &'a UserDataMap,
173    r: &'a U,
174}
175
176impl<'a, U> Deref for UserDataGuard<'a, U> {
177    type Target = U;
178
179    fn deref(&self) -> &Self::Target {
180        self.r
181    }
182}
183
184impl<'a, U> Drop for UserDataGuard<'a, U> {
185    fn drop(&mut self) {
186        self.map.count.set(self.map.count.get() - 1)
187    }
188}