|
| 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 | +} |
0 commit comments