1use alloc::{borrow::ToOwned, boxed::Box};
8use core::{fmt::Debug, hash::Hash, ops::Deref};
9
10use bevy_platform::{
11 collections::HashSet,
12 hash::FixedHasher,
13 sync::{PoisonError, RwLock},
14};
15#[cfg(feature = "bevy_reflect")]
16use bevy_reflect::Reflect;
17
18#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
46#[cfg_attr(feature = "bevy_reflect", reflect(Clone, PartialEq, Hash))]
47pub struct Interned<T: ?Sized + Internable + 'static>(pub &'static T);
48
49impl<T: ?Sized + Internable> Deref for Interned<T> {
50 type Target = T;
51
52 fn deref(&self) -> &Self::Target {
53 self.0
54 }
55}
56
57impl<T: ?Sized + Internable> Clone for Interned<T> {
58 fn clone(&self) -> Self {
59 *self
60 }
61}
62
63impl<T: ?Sized + Internable> Copy for Interned<T> {}
64
65impl<T: ?Sized + Internable> PartialEq for Interned<T> {
68 fn eq(&self, other: &Self) -> bool {
69 self.0.ref_eq(other.0)
70 }
71}
72
73impl<T: ?Sized + Internable> Eq for Interned<T> {}
74
75impl<T: ?Sized + Internable> Hash for Interned<T> {
77 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
78 self.0.ref_hash(state);
79 }
80}
81
82impl<T: ?Sized + Internable + Debug> Debug for Interned<T> {
83 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84 self.0.fmt(f)
85 }
86}
87
88impl<T: ?Sized + Internable> From<&Interned<T>> for Interned<T> {
89 fn from(value: &Interned<T>) -> Self {
90 *value
91 }
92}
93
94pub trait Internable: Hash + Eq {
98 fn leak(&self) -> &'static Self;
100
101 fn ref_eq(&self, other: &Self) -> bool;
103
104 fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H);
106}
107
108impl Internable for str {
109 fn leak(&self) -> &'static Self {
110 let str = self.to_owned().into_boxed_str();
111 Box::leak(str)
112 }
113
114 fn ref_eq(&self, other: &Self) -> bool {
115 self.as_ptr() == other.as_ptr() && self.len() == other.len()
116 }
117
118 fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H) {
119 self.len().hash(state);
120 self.as_ptr().hash(state);
121 }
122}
123
124pub struct Interner<T: ?Sized + 'static>(RwLock<HashSet<&'static T>>);
132
133impl<T: ?Sized> Interner<T> {
134 pub const fn new() -> Self {
136 Self(RwLock::new(HashSet::with_hasher(FixedHasher)))
137 }
138}
139
140impl<T: Internable + ?Sized> Interner<T> {
141 pub fn intern(&self, value: &T) -> Interned<T> {
147 {
148 let set = self.0.read().unwrap_or_else(PoisonError::into_inner);
149
150 if let Some(value) = set.get(value) {
151 return Interned(*value);
152 }
153 }
154
155 {
156 let mut set = self.0.write().unwrap_or_else(PoisonError::into_inner);
157
158 if let Some(value) = set.get(value) {
159 Interned(*value)
160 } else {
161 let leaked = value.leak();
162 set.insert(leaked);
163 Interned(leaked)
164 }
165 }
166 }
167}
168
169impl<T: ?Sized> Default for Interner<T> {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use alloc::{boxed::Box, string::ToString};
178 use bevy_platform::hash::FixedHasher;
179 use core::hash::{BuildHasher, Hash, Hasher};
180
181 use crate::intern::{Internable, Interned, Interner};
182
183 #[test]
184 fn zero_sized_type() {
185 #[derive(PartialEq, Eq, Hash, Debug)]
186 pub struct A;
187
188 impl Internable for A {
189 fn leak(&self) -> &'static Self {
190 &A
191 }
192
193 fn ref_eq(&self, other: &Self) -> bool {
194 core::ptr::eq(self, other)
195 }
196
197 fn ref_hash<H: Hasher>(&self, state: &mut H) {
198 core::ptr::hash(self, state);
199 }
200 }
201
202 let interner = Interner::default();
203 let x = interner.intern(&A);
204 let y = interner.intern(&A);
205 assert_eq!(x, y);
206 }
207
208 #[test]
209 fn fieldless_enum() {
210 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
211 pub enum A {
212 X,
213 Y,
214 }
215
216 impl Internable for A {
217 fn leak(&self) -> &'static Self {
218 match self {
219 A::X => &A::X,
220 A::Y => &A::Y,
221 }
222 }
223
224 fn ref_eq(&self, other: &Self) -> bool {
225 core::ptr::eq(self, other)
226 }
227
228 fn ref_hash<H: Hasher>(&self, state: &mut H) {
229 core::ptr::hash(self, state);
230 }
231 }
232
233 let interner = Interner::default();
234 let x = interner.intern(&A::X);
235 let y = interner.intern(&A::Y);
236 assert_ne!(x, y);
237 }
238
239 #[test]
240 fn static_sub_strings() {
241 let str = "ABC ABC";
242 let a = &str[0..3];
243 let b = &str[4..7];
244 assert_eq!(a, b);
246 let x = Interned(a);
247 let y = Interned(b);
248 assert_ne!(x, y);
250 let interner = Interner::default();
251 let x = interner.intern(a);
252 let y = interner.intern(b);
253 assert_eq!(x, y);
255 }
256
257 #[test]
258 fn same_interned_instance() {
259 let a = Interned("A");
260 let b = a;
261
262 assert_eq!(a, b);
263
264 let hash_a = FixedHasher.hash_one(a);
265 let hash_b = FixedHasher.hash_one(b);
266
267 assert_eq!(hash_a, hash_b);
268 }
269
270 #[test]
271 fn same_interned_content() {
272 let a = Interned::<str>(Box::leak(Box::new("A".to_string())));
273 let b = Interned::<str>(Box::leak(Box::new("A".to_string())));
274
275 assert_ne!(a, b);
276 }
277
278 #[test]
279 fn different_interned_content() {
280 let a = Interned::<str>("A");
281 let b = Interned::<str>("B");
282
283 assert_ne!(a, b);
284 }
285}