Skip to content

Commit 1cd6031

Browse files
committed
Add types for statically interning values
1 parent 76062ff commit 1cd6031

File tree

3 files changed

+56
-0
lines changed

3 files changed

+56
-0
lines changed

crates/bevy_utils/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
1414
instant = { version = "0.1", features = ["wasm-bindgen"] }
1515
uuid = { version = "1.1", features = ["v4", "serde"] }
1616
hashbrown = { version = "0.12", features = ["serde"] }
17+
indexmap = "1.9"
18+
parking_lot = "0.12"
1719

1820
[target.'cfg(target_arch = "wasm32")'.dependencies]
1921
getrandom = {version = "0.2.0", features = ["js"]}

crates/bevy_utils/src/intern.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::hash::Hash;
2+
3+
use parking_lot::{RwLock, RwLockReadGuard};
4+
5+
use crate::FixedState;
6+
7+
type IndexSet<T> = indexmap::IndexSet<T, FixedState>;
8+
9+
/// A data structure used to intern a set of values of a specific type.
10+
/// To store multiple distinct types, or generic types, try [`AnyInterner`].
11+
pub struct Interner<T: Clone + Hash + Eq>(
12+
// The `IndexSet` is a hash set that preserves ordering as long as
13+
// you don't remove items (which we don't).
14+
// This allows us to have O(~1) hashing and map each entry to a stable index.
15+
RwLock<IndexSet<T>>,
16+
);
17+
18+
/// The type returned from [`Interner::get`](Interner#method.get).
19+
///
20+
/// Will hold a lock on the interner until this guard gets dropped.
21+
pub type InternGuard<'a, L> = parking_lot::MappedRwLockReadGuard<'a, L>;
22+
23+
impl<T: Clone + Hash + Eq> Interner<T> {
24+
pub const fn new() -> Self {
25+
Self(RwLock::new(IndexSet::with_hasher(FixedState)))
26+
}
27+
28+
/// Interns a value, if it was not already interned in this set.
29+
///
30+
/// Returns an integer used to refer to the value later on.
31+
pub fn intern(&self, val: &T) -> usize {
32+
use parking_lot::RwLockUpgradableReadGuard as Guard;
33+
34+
// Acquire an upgradeable read lock, since we might not have to do any writing.
35+
let set = self.0.upgradable_read();
36+
37+
// If the value is already interned, return its index.
38+
if let Some(idx) = set.get_index_of(val) {
39+
return idx;
40+
}
41+
42+
// Upgrade to a mutable lock.
43+
let mut set = Guard::upgrade(set);
44+
let (idx, _) = set.insert_full(val.clone());
45+
idx
46+
}
47+
48+
/// Gets a reference to the value with specified index.
49+
pub fn get(&self, idx: usize) -> Option<InternGuard<T>> {
50+
RwLockReadGuard::try_map(self.0.read(), |set| set.get_index(idx)).ok()
51+
}
52+
}

crates/bevy_utils/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ pub use short_names::get_short_name;
99

1010
mod default;
1111
mod float_ord;
12+
mod intern;
1213

1314
pub use ahash::AHasher;
1415
pub use default::default;
1516
pub use float_ord::*;
1617
pub use hashbrown;
1718
pub use instant::{Duration, Instant};
19+
pub use intern::*;
1820
pub use tracing;
1921
pub use uuid::Uuid;
2022

0 commit comments

Comments
 (0)