use std::{
fmt::{self, Debug},
hash::{Hash, Hasher},
sync::OnceLock,
};
use dashmap::{DashMap, SharedValue};
use rustc_hash::FxBuildHasher;
use triomphe::ThinArc;
type InternMap<T> =
DashMap<ThinArc<<T as SliceInternable>::Header, <T as SliceInternable>::Item>, (), FxBuildHasher>;
pub struct InternedSlice<T: SliceInternable> {
arc: ThinArc<T::Header, T::Item>,
}
impl<T: SliceInternable> InternedSlice<T> {
pub fn new(header: T::Header, items: &[T::Item]) -> Self {
let storage = T::storage().get();
let hash = T::hash(&header, items);
let shard = &storage.shards()[storage.determine_shard(hash as usize)];
{
let shard = shard.read();
if let Some(bucket) = shard.find(hash, |(other, _)| T::eq(&other.slice, items)) {
return unsafe {
Self {
arc: bucket.as_ref().0.clone(),
}
};
}
}
let mut shard = shard.write();
let bucket = match shard.find_or_find_insert_slot(
hash,
|(other, _)| T::eq(&other.slice, items),
|(other, _)| T::hash(&other.header.header, &other.slice),
) {
Ok(bucket) => bucket,
Err(insert_slot) => unsafe {
shard.insert_in_slot(
hash,
insert_slot,
(
ThinArc::from_header_and_slice(header, items),
SharedValue::new(()),
),
)
},
};
unsafe {
Self {
arc: bucket.as_ref().0.clone(),
}
}
}
#[inline]
pub fn header(&self) -> &T::Header {
&self.arc.header.header
}
#[inline]
pub fn items(&self) -> &[T::Item] {
&self.arc.slice
}
#[cold]
fn drop_slow(&mut self) {
let storage = T::storage().get();
let hash = T::hash(self.header(), self.items());
let mut shard = storage.shards()[storage.determine_shard(hash as usize)].write();
if ThinArc::strong_count(&self.arc) != 2 {
return;
}
let this = self.arc.as_ptr();
shard.remove_entry(hash, |(other, _)| other.as_ptr() == this);
if shard.len() * 2 < shard.capacity() {
let len = shard.len();
shard.shrink_to(len, |(other, _)| {
T::hash(&other.header.header, &other.slice)
});
}
}
}
impl<T: SliceInternable> Drop for InternedSlice<T> {
#[inline]
fn drop(&mut self) {
if ThinArc::strong_count(&self.arc) == 2 {
self.drop_slow();
}
}
}
impl<T: SliceInternable> PartialEq for InternedSlice<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.arc.as_ptr() == other.arc.as_ptr()
}
}
impl<T: SliceInternable> Eq for InternedSlice<T> {}
impl<T: SliceInternable> Hash for InternedSlice<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.arc.as_ptr().addr() as u64)
}
}
impl<T: SliceInternable> Clone for InternedSlice<T> {
#[inline]
fn clone(&self) -> Self {
Self {
arc: self.arc.clone(),
}
}
}
impl<T> Debug for InternedSlice<T>
where
T: SliceInternable,
T::Header: Debug,
T::Item: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InternedSlice")
.field("header", self.header())
.field("items", &self.items())
.finish()
}
}
pub struct InternSliceStorage<T: SliceInternable> {
map: OnceLock<InternMap<T>>,
}
#[allow(
clippy::new_without_default,
reason = "this a const fn, so it can't be default yet. See <https://github.com/rust-lang/rust/issues/63065>"
)]
impl<T: SliceInternable> InternSliceStorage<T> {
pub const fn new() -> Self {
Self {
map: OnceLock::new(),
}
}
fn get(&self) -> &InternMap<T> {
self
.map
.get_or_init(|| DashMap::with_capacity_and_hasher(1024, FxBuildHasher))
}
pub fn len(&self) -> usize {
self.get().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub trait SliceInternable: Sized + Send + Sync + 'static {
type Header: Eq + Hash + Send + Sync;
type Item: Copy + Eq + Hash + Send + Sync;
fn hash(header: &Self::Header, items: &[Self::Item]) -> u64;
fn eq(a: &[Self::Item], b: &[Self::Item]) -> bool;
fn storage() -> &'static InternSliceStorage<Self>;
}