rquickjs_core/runtime/
userdata.rsuse core::fmt;
use std::{
any::{Any, TypeId},
cell::{Cell, UnsafeCell},
collections::HashMap,
hash::{BuildHasherDefault, Hasher},
mem::ManuallyDrop,
ops::Deref,
};
use crate::JsLifetime;
unsafe fn to_static<'js, T: JsLifetime<'js>>(this: T) -> T::Changed<'static>
where
T: Sized,
{
assert_eq!(
std::mem::size_of::<T>(),
std::mem::size_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
);
assert_eq!(
std::mem::align_of::<T>(),
std::mem::align_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
);
union Trans<A, B> {
from: ManuallyDrop<A>,
to: ManuallyDrop<B>,
}
ManuallyDrop::into_inner(
(Trans {
from: ManuallyDrop::new(this),
})
.to,
)
}
unsafe fn from_static_box<'js, T: JsLifetime<'js>>(this: Box<T::Changed<'static>>) -> Box<T>
where
T: Sized,
{
assert_eq!(
std::mem::size_of::<T>(),
std::mem::size_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, size_of::<T>() != size_of::<T::Changed<'static>>()"
);
assert_eq!(
std::mem::align_of::<T>(),
std::mem::align_of::<T::Changed<'static>>(),
"Invalid implementation of JsLifetime, align_of::<T>() != align_of::<T::Changed<'static>>()"
);
Box::from_raw(Box::into_raw(this) as *mut T)
}
unsafe fn from_static_ref<'a, 'js, T: JsLifetime<'js>>(this: &'a T::Changed<'static>) -> &'a T
where
T: Sized,
{
std::mem::transmute(this)
}
pub struct UserDataError<T>(pub T);
impl<T> fmt::Display for UserDataError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"tried to mutate the user data store while it was being referenced"
)
}
}
impl<T> fmt::Debug for UserDataError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Default)]
struct IdHasher(u64);
impl Hasher for IdHasher {
fn write(&mut self, _: &[u8]) {
unreachable!("TypeId calls write_u64");
}
fn write_u64(&mut self, id: u64) {
self.0 = id;
}
fn finish(&self) -> u64 {
self.0
}
}
#[derive(Default)]
pub(crate) struct UserDataMap {
map: UnsafeCell<HashMap<TypeId, Box<dyn Any>, BuildHasherDefault<IdHasher>>>,
count: Cell<usize>,
}
impl UserDataMap {
pub fn insert<'js, U>(&self, data: U) -> Result<Option<Box<U>>, UserDataError<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
if self.count.get() > 0 {
return Err(UserDataError(data));
}
let user_static = unsafe { to_static(data) };
let id = TypeId::of::<U::Changed<'static>>();
let r = unsafe { (*self.map.get()).insert(id, Box::new(user_static)) }.map(|x| {
let r = x
.downcast()
.expect("type confusion! userdata not stored under the right type id");
unsafe { from_static_box(r) }
});
Ok(r)
}
pub fn remove<'js, U>(&self) -> Result<Option<Box<U>>, UserDataError<()>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
if self.count.get() > 0 {
return Err(UserDataError(()));
}
let id = TypeId::of::<U::Changed<'static>>();
let r = unsafe { (*self.map.get()).remove(&id) }.map(|x| {
let r = x
.downcast()
.expect("type confusion! userdata not stored under the right type id");
unsafe { from_static_box(r) }
});
Ok(r)
}
pub fn get<'js, U>(&self) -> Option<UserDataGuard<U>>
where
U: JsLifetime<'js>,
U::Changed<'static>: Any,
{
let id = TypeId::of::<U::Changed<'static>>();
unsafe { (*self.map.get()).get(&id) }.map(|x| {
self.count.set(self.count.get() + 1);
let u = x
.downcast_ref()
.expect("type confusion! userdata not stored under the right type id");
let r = unsafe { from_static_ref(u) };
UserDataGuard { map: self, r }
})
}
pub fn clear(&mut self) {
self.map.get_mut().clear()
}
}
pub struct UserDataGuard<'a, U> {
map: &'a UserDataMap,
r: &'a U,
}
impl<'a, U> Deref for UserDataGuard<'a, U> {
type Target = U;
fn deref(&self) -> &Self::Target {
self.r
}
}
impl<'a, U> Drop for UserDataGuard<'a, U> {
fn drop(&mut self) {
self.map.count.set(self.map.count.get() - 1)
}
}