Skip to content

Commit bf41520

Browse files
committed
Add a type for interning type-erased values
1 parent ec7bfd9 commit bf41520

File tree

1 file changed

+85
-2
lines changed

1 file changed

+85
-2
lines changed

crates/bevy_utils/src/intern.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::hash::Hash;
1+
use std::{any::TypeId, hash::Hash};
22

33
use parking_lot::{RwLock, RwLockReadGuard};
44

5-
use crate::FixedState;
5+
use crate::{FixedState, StableHashMap};
66

77
type IndexSet<T> = indexmap::IndexSet<T, FixedState>;
88

@@ -50,3 +50,86 @@ impl<T: Clone + Hash + Eq> Interner<T> {
5050
RwLockReadGuard::try_map(self.0.read(), |set| set.get_index(idx)).ok()
5151
}
5252
}
53+
54+
struct TypeMap(StableHashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>);
55+
56+
impl TypeMap {
57+
pub const fn new() -> Self {
58+
Self(StableHashMap::with_hasher(FixedState))
59+
}
60+
61+
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<impl Drop> {
62+
self.0.insert(TypeId::of::<T>(), Box::new(val))
63+
}
64+
pub fn get<T: 'static>(&self) -> Option<&T> {
65+
let val = self.0.get(&TypeId::of::<T>())?.as_ref();
66+
// SAFETY: `val` was keyed with the TypeId of `T`, so we can cast it to `T`.
67+
Some(unsafe { &*(val as *const _ as *const T) })
68+
}
69+
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
70+
let val = self.0.get_mut(&TypeId::of::<T>())?.as_mut();
71+
// SAFETY: `val` was keyed with the TypeId of `T`, so we can cast it to `T`.
72+
Some(unsafe { &mut *(val as *mut _ as *mut T) })
73+
}
74+
}
75+
76+
/// Data structure used to intern a set of values of any given type.
77+
///
78+
/// If you just need to store a single concrete type, [`Interner`] is more efficient.
79+
pub struct AnyInterner(
80+
// This type-map stores instances of `IndexSet<T>`, for any `T`.
81+
RwLock<TypeMap>,
82+
);
83+
84+
impl AnyInterner {
85+
pub const fn new() -> Self {
86+
Self(RwLock::new(TypeMap::new()))
87+
}
88+
89+
/// Interns a value, if it was not already interned in this set.
90+
///
91+
/// Returns an integer used to refer to the value later on.
92+
pub fn intern<L>(&self, val: &L) -> usize
93+
where
94+
L: Clone + Hash + Eq + Send + Sync + 'static,
95+
{
96+
use parking_lot::RwLockUpgradableReadGuard as Guard;
97+
98+
// Acquire an upgradeable read lock, since we might not have to do any writing.
99+
let type_map = self.0.upgradable_read();
100+
101+
if let Some(set) = type_map.get::<IndexSet<L>>() {
102+
// If the value is already interned, return its index.
103+
if let Some(idx) = set.get_index_of(val) {
104+
return idx;
105+
}
106+
107+
// Get mutable access to the interner.
108+
let mut type_map = Guard::upgrade(type_map);
109+
let set = type_map.get_mut::<IndexSet<L>>().unwrap();
110+
111+
// Insert a clone of the value and return its index.
112+
let (idx, _) = set.insert_full(val.clone());
113+
idx
114+
} else {
115+
let mut type_map = Guard::upgrade(type_map);
116+
117+
// Initialize the `L` interner for the first time, including `val` in it.
118+
let mut set = IndexSet::default();
119+
let (idx, _) = set.insert_full(val.clone());
120+
let old = type_map.insert(set);
121+
// We already checked that there is no set for type `L`,
122+
// so let's avoid generating useless drop code for the "previous" entry.
123+
std::mem::forget(old);
124+
idx
125+
}
126+
}
127+
128+
/// Gets a reference to the value with specified index.
129+
pub fn get<L: 'static>(&self, key: usize) -> Option<InternGuard<L>> {
130+
RwLockReadGuard::try_map(self.0.read(), |type_map| {
131+
type_map.get::<IndexSet<L>>()?.get_index(key)
132+
})
133+
.ok()
134+
}
135+
}

0 commit comments

Comments
 (0)