#![doc = include_str!("../README.md")]
#![deny(missing_docs, clippy::all)]
#![allow(clippy::needless_doctest_main)]
mod profiling;
mod unwrap_dbg;
use crate::unwrap_dbg::UnwrapDbg;
use std::hash::Hash;
use std::sync::atomic::{AtomicU64, Ordering};
use dashmap::DashMap;
use ecow::EcoString;
use papaya::LocalGuard;
use rustc_hash::FxBuildHasher;
impl nohash_hasher::IsEnabled for AtomId {}
type NoHashDashMap<K, V> = DashMap<K, V, nohash_hasher::BuildNoHashHasher<K>>;
type FxHashPapayaHashMap<K, V> = papaya::HashMap<K, V, rustc_hash::FxBuildHasher>;
type StrToIdMapRef<'map, 'g> =
papaya::HashMapRef<'map, EcoString, AtomId, FxBuildHasher, LocalGuard<'g>>;
pub mod prelude {
pub use crate::{AtomId, AtomTable};
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub struct AtomId(pub u64);
#[derive(Debug, Default)]
pub struct AtomTable {
atom_id_to_str: NoHashDashMap<AtomId, EcoString>,
str_to_atom_id: FxHashPapayaHashMap<EcoString, AtomId>,
next_atom_id: AtomicU64,
}
impl Clone for AtomTable {
fn clone(&self) -> Self {
Self {
str_to_atom_id: self.str_to_atom_id.clone(),
atom_id_to_str: self.atom_id_to_str.clone(),
next_atom_id: AtomicU64::new(self.next_atom_id.load(Ordering::Relaxed)),
}
}
}
impl AtomTable {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
str_to_atom_id: FxHashPapayaHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
atom_id_to_str: NoHashDashMap::with_capacity_and_hasher(
capacity,
nohash_hasher::BuildNoHashHasher::default(),
),
next_atom_id: AtomicU64::new(0),
}
}
#[inline(always)]
pub fn intern(&self, s: &str) -> AtomId {
let _span = tracy_span!("AtomTable::intern");
let g = self.str_to_atom_id.pin();
if let Some(&id) = g.get(s) {
return id;
}
self.intern_cold(s, &g)
}
#[inline]
pub fn intern_with_guard(&self, s: &str, g: &StrToIdMapRef<'_, '_>) -> AtomId {
if let Some(&id) = g.get(s) {
return id;
}
self.intern_cold(s, g)
}
#[inline]
pub fn intern_batch<'a, I>(&self, strings: I) -> Vec<AtomId>
where
I: IntoIterator<Item = &'a str>,
I::IntoIter: ExactSizeIterator,
{
let iter = strings.into_iter();
let mut result = Vec::with_capacity(iter.len());
let g = self.str_to_atom_id.pin();
for s in iter {
result.push(self.intern_with_guard(s, &g));
}
result
}
#[cold]
#[inline(never)]
fn intern_cold(&self, s: &str, g: &StrToIdMapRef<'_, '_>) -> AtomId {
let _span = tracy_span!("AtomTable::intern_cold");
let key = EcoString::from(s);
let key_ptr: *const EcoString = &key;
*g.get_or_insert_with(key, || {
let id = AtomId(self.next_atom_id.fetch_add(1, Ordering::Relaxed));
unsafe {
self.atom_id_to_str.insert(id, EcoString::clone(&*key_ptr));
}
id
})
}
#[inline]
pub fn pin(&self) -> StrToIdMapRef<'_, '_> {
self.str_to_atom_id.pin()
}
#[inline]
#[must_use]
pub fn lookup_owned(&self, id: AtomId) -> EcoString {
EcoString::clone(&*self.atom_id_to_str.get(&id).unwrap_dbg())
}
#[inline]
#[must_use]
pub fn lookup_ref(&self, id: AtomId) -> dashmap::mapref::one::Ref<'_, AtomId, EcoString> {
self.atom_id_to_str.get(&id).unwrap_dbg()
}
#[inline]
#[must_use]
pub fn try_lookup_id(&self, s: &str) -> Option<AtomId> {
self.str_to_atom_id.pin().get(s).copied()
}
#[inline]
#[must_use]
pub fn try_lookup_id_with_guard(&self, s: &str, g: &StrToIdMapRef<'_, '_>) -> Option<AtomId> {
g.get(s).copied()
}
#[inline]
#[must_use]
pub fn lookup_id(&self, s: &str) -> AtomId {
self.try_lookup_id(s).unwrap_dbg()
}
#[inline]
#[must_use]
pub fn lookup_id_with_guard(&self, s: &str, g: &StrToIdMapRef<'_, '_>) -> AtomId {
self.try_lookup_id_with_guard(s, g).unwrap_dbg()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.str_to_atom_id.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.str_to_atom_id.is_empty()
}
#[inline]
pub fn clear(&self) {
self.str_to_atom_id.pin().clear();
self.atom_id_to_str.clear();
self.next_atom_id.store(0, Ordering::Relaxed);
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (AtomId, EcoString)> + '_ {
self.atom_id_to_str
.iter()
.map(|r| (*r.key(), EcoString::clone(&*r)))
}
}