Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/intern.rs
6601 views
1
//! Provides types used to statically intern immutable values.
2
//!
3
//! Interning is a pattern used to save memory by deduplicating identical values,
4
//! speed up code by shrinking the stack size of large types,
5
//! and make comparisons for any type as fast as integers.
6
7
use alloc::{borrow::ToOwned, boxed::Box};
8
use bevy_platform::{
9
collections::HashSet,
10
hash::FixedHasher,
11
sync::{PoisonError, RwLock},
12
};
13
use core::{fmt::Debug, hash::Hash, ops::Deref};
14
15
/// An interned value. Will stay valid until the end of the program and will not drop.
16
///
17
/// For details on interning, see [the module level docs](self).
18
///
19
/// # Comparisons
20
///
21
/// Interned values use reference equality, meaning they implement [`Eq`]
22
/// and [`Hash`] regardless of whether `T` implements these traits.
23
/// Two interned values are only guaranteed to compare equal if they were interned using
24
/// the same [`Interner`] instance.
25
// NOTE: This type must NEVER implement Borrow since it does not obey that trait's invariants.
26
/// ```
27
/// # use bevy_ecs::intern::*;
28
/// #[derive(PartialEq, Eq, Hash, Debug)]
29
/// struct Value(i32);
30
/// impl Internable for Value {
31
/// // ...
32
/// # fn leak(&self) -> &'static Self { Box::leak(Box::new(Value(self.0))) }
33
/// # fn ref_eq(&self, other: &Self) -> bool { std::ptr::eq(self, other ) }
34
/// # fn ref_hash<H: std::hash::Hasher>(&self, state: &mut H) { std::ptr::hash(self, state); }
35
/// }
36
/// let interner_1 = Interner::new();
37
/// let interner_2 = Interner::new();
38
/// // Even though both values are identical, their interned forms do not
39
/// // compare equal as they use different interner instances.
40
/// assert_ne!(interner_1.intern(&Value(42)), interner_2.intern(&Value(42)));
41
/// ```
42
pub struct Interned<T: ?Sized + 'static>(pub &'static T);
43
44
impl<T: ?Sized> Deref for Interned<T> {
45
type Target = T;
46
47
fn deref(&self) -> &Self::Target {
48
self.0
49
}
50
}
51
52
impl<T: ?Sized> Clone for Interned<T> {
53
fn clone(&self) -> Self {
54
*self
55
}
56
}
57
58
impl<T: ?Sized> Copy for Interned<T> {}
59
60
// Two Interned<T> should only be equal if they are clones from the same instance.
61
// Therefore, we only use the pointer to determine equality.
62
impl<T: ?Sized + Internable> PartialEq for Interned<T> {
63
fn eq(&self, other: &Self) -> bool {
64
self.0.ref_eq(other.0)
65
}
66
}
67
68
impl<T: ?Sized + Internable> Eq for Interned<T> {}
69
70
// Important: This must be kept in sync with the PartialEq/Eq implementation
71
impl<T: ?Sized + Internable> Hash for Interned<T> {
72
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
73
self.0.ref_hash(state);
74
}
75
}
76
77
impl<T: ?Sized + Debug> Debug for Interned<T> {
78
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79
self.0.fmt(f)
80
}
81
}
82
83
impl<T> From<&Interned<T>> for Interned<T> {
84
fn from(value: &Interned<T>) -> Self {
85
*value
86
}
87
}
88
89
/// A trait for internable values.
90
///
91
/// This is used by [`Interner<T>`] to create static references for values that are interned.
92
pub trait Internable: Hash + Eq {
93
/// Creates a static reference to `self`, possibly leaking memory.
94
fn leak(&self) -> &'static Self;
95
96
/// Returns `true` if the two references point to the same value.
97
fn ref_eq(&self, other: &Self) -> bool;
98
99
/// Feeds the reference to the hasher.
100
fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H);
101
}
102
103
impl Internable for str {
104
fn leak(&self) -> &'static Self {
105
let str = self.to_owned().into_boxed_str();
106
Box::leak(str)
107
}
108
109
fn ref_eq(&self, other: &Self) -> bool {
110
self.as_ptr() == other.as_ptr() && self.len() == other.len()
111
}
112
113
fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H) {
114
self.len().hash(state);
115
self.as_ptr().hash(state);
116
}
117
}
118
119
/// A thread-safe interner which can be used to create [`Interned<T>`] from `&T`
120
///
121
/// For details on interning, see [the module level docs](self).
122
///
123
/// The implementation ensures that two equal values return two equal [`Interned<T>`] values.
124
///
125
/// To use an [`Interner<T>`], `T` must implement [`Internable`].
126
pub struct Interner<T: ?Sized + 'static>(RwLock<HashSet<&'static T>>);
127
128
impl<T: ?Sized> Interner<T> {
129
/// Creates a new empty interner
130
pub const fn new() -> Self {
131
Self(RwLock::new(HashSet::with_hasher(FixedHasher)))
132
}
133
}
134
135
impl<T: Internable + ?Sized> Interner<T> {
136
/// Return the [`Interned<T>`] corresponding to `value`.
137
///
138
/// If it is called the first time for `value`, it will possibly leak the value and return an
139
/// [`Interned<T>`] using the obtained static reference. Subsequent calls for the same `value`
140
/// will return [`Interned<T>`] using the same static reference.
141
pub fn intern(&self, value: &T) -> Interned<T> {
142
{
143
let set = self.0.read().unwrap_or_else(PoisonError::into_inner);
144
145
if let Some(value) = set.get(value) {
146
return Interned(*value);
147
}
148
}
149
150
{
151
let mut set = self.0.write().unwrap_or_else(PoisonError::into_inner);
152
153
if let Some(value) = set.get(value) {
154
Interned(*value)
155
} else {
156
let leaked = value.leak();
157
set.insert(leaked);
158
Interned(leaked)
159
}
160
}
161
}
162
}
163
164
impl<T: ?Sized> Default for Interner<T> {
165
fn default() -> Self {
166
Self::new()
167
}
168
}
169
170
#[cfg(test)]
171
mod tests {
172
use alloc::{boxed::Box, string::ToString};
173
use bevy_platform::hash::FixedHasher;
174
use core::hash::{BuildHasher, Hash, Hasher};
175
176
use crate::intern::{Internable, Interned, Interner};
177
178
#[test]
179
fn zero_sized_type() {
180
#[derive(PartialEq, Eq, Hash, Debug)]
181
pub struct A;
182
183
impl Internable for A {
184
fn leak(&self) -> &'static Self {
185
&A
186
}
187
188
fn ref_eq(&self, other: &Self) -> bool {
189
core::ptr::eq(self, other)
190
}
191
192
fn ref_hash<H: Hasher>(&self, state: &mut H) {
193
core::ptr::hash(self, state);
194
}
195
}
196
197
let interner = Interner::default();
198
let x = interner.intern(&A);
199
let y = interner.intern(&A);
200
assert_eq!(x, y);
201
}
202
203
#[test]
204
fn fieldless_enum() {
205
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
206
pub enum A {
207
X,
208
Y,
209
}
210
211
impl Internable for A {
212
fn leak(&self) -> &'static Self {
213
match self {
214
A::X => &A::X,
215
A::Y => &A::Y,
216
}
217
}
218
219
fn ref_eq(&self, other: &Self) -> bool {
220
core::ptr::eq(self, other)
221
}
222
223
fn ref_hash<H: Hasher>(&self, state: &mut H) {
224
core::ptr::hash(self, state);
225
}
226
}
227
228
let interner = Interner::default();
229
let x = interner.intern(&A::X);
230
let y = interner.intern(&A::Y);
231
assert_ne!(x, y);
232
}
233
234
#[test]
235
fn static_sub_strings() {
236
let str = "ABC ABC";
237
let a = &str[0..3];
238
let b = &str[4..7];
239
// Same contents
240
assert_eq!(a, b);
241
let x = Interned(a);
242
let y = Interned(b);
243
// Different pointers
244
assert_ne!(x, y);
245
let interner = Interner::default();
246
let x = interner.intern(a);
247
let y = interner.intern(b);
248
// Same pointers returned by interner
249
assert_eq!(x, y);
250
}
251
252
#[test]
253
fn same_interned_instance() {
254
let a = Interned("A");
255
let b = a;
256
257
assert_eq!(a, b);
258
259
let hash_a = FixedHasher.hash_one(a);
260
let hash_b = FixedHasher.hash_one(b);
261
262
assert_eq!(hash_a, hash_b);
263
}
264
265
#[test]
266
fn same_interned_content() {
267
let a = Interned::<str>(Box::leak(Box::new("A".to_string())));
268
let b = Interned::<str>(Box::leak(Box::new("A".to_string())));
269
270
assert_ne!(a, b);
271
}
272
273
#[test]
274
fn different_interned_content() {
275
let a = Interned::<str>("A");
276
let b = Interned::<str>("B");
277
278
assert_ne!(a, b);
279
}
280
}
281
282