use alloc::{borrow::ToOwned, boxed::Box};
use bevy_platform::{
collections::HashSet,
hash::FixedHasher,
sync::{PoisonError, RwLock},
};
use core::{fmt::Debug, hash::Hash, ops::Deref};
pub struct Interned<T: ?Sized + 'static>(pub &'static T);
impl<T: ?Sized> Deref for Interned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl<T: ?Sized> Clone for Interned<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized> Copy for Interned<T> {}
impl<T: ?Sized + Internable> PartialEq for Interned<T> {
fn eq(&self, other: &Self) -> bool {
self.0.ref_eq(other.0)
}
}
impl<T: ?Sized + Internable> Eq for Interned<T> {}
impl<T: ?Sized + Internable> Hash for Interned<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.ref_hash(state);
}
}
impl<T: ?Sized + Debug> Debug for Interned<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl<T> From<&Interned<T>> for Interned<T> {
fn from(value: &Interned<T>) -> Self {
*value
}
}
pub trait Internable: Hash + Eq {
fn leak(&self) -> &'static Self;
fn ref_eq(&self, other: &Self) -> bool;
fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H);
}
impl Internable for str {
fn leak(&self) -> &'static Self {
let str = self.to_owned().into_boxed_str();
Box::leak(str)
}
fn ref_eq(&self, other: &Self) -> bool {
self.as_ptr() == other.as_ptr() && self.len() == other.len()
}
fn ref_hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.len().hash(state);
self.as_ptr().hash(state);
}
}
pub struct Interner<T: ?Sized + 'static>(RwLock<HashSet<&'static T>>);
impl<T: ?Sized> Interner<T> {
pub const fn new() -> Self {
Self(RwLock::new(HashSet::with_hasher(FixedHasher)))
}
}
impl<T: Internable + ?Sized> Interner<T> {
pub fn intern(&self, value: &T) -> Interned<T> {
{
let set = self.0.read().unwrap_or_else(PoisonError::into_inner);
if let Some(value) = set.get(value) {
return Interned(*value);
}
}
{
let mut set = self.0.write().unwrap_or_else(PoisonError::into_inner);
if let Some(value) = set.get(value) {
Interned(*value)
} else {
let leaked = value.leak();
set.insert(leaked);
Interned(leaked)
}
}
}
}
impl<T: ?Sized> Default for Interner<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use alloc::{boxed::Box, string::ToString};
use bevy_platform::hash::FixedHasher;
use core::hash::{BuildHasher, Hash, Hasher};
use crate::intern::{Internable, Interned, Interner};
#[test]
fn zero_sized_type() {
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct A;
impl Internable for A {
fn leak(&self) -> &'static Self {
&A
}
fn ref_eq(&self, other: &Self) -> bool {
core::ptr::eq(self, other)
}
fn ref_hash<H: Hasher>(&self, state: &mut H) {
core::ptr::hash(self, state);
}
}
let interner = Interner::default();
let x = interner.intern(&A);
let y = interner.intern(&A);
assert_eq!(x, y);
}
#[test]
fn fieldless_enum() {
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum A {
X,
Y,
}
impl Internable for A {
fn leak(&self) -> &'static Self {
match self {
A::X => &A::X,
A::Y => &A::Y,
}
}
fn ref_eq(&self, other: &Self) -> bool {
core::ptr::eq(self, other)
}
fn ref_hash<H: Hasher>(&self, state: &mut H) {
core::ptr::hash(self, state);
}
}
let interner = Interner::default();
let x = interner.intern(&A::X);
let y = interner.intern(&A::Y);
assert_ne!(x, y);
}
#[test]
fn static_sub_strings() {
let str = "ABC ABC";
let a = &str[0..3];
let b = &str[4..7];
assert_eq!(a, b);
let x = Interned(a);
let y = Interned(b);
assert_ne!(x, y);
let interner = Interner::default();
let x = interner.intern(a);
let y = interner.intern(b);
assert_eq!(x, y);
}
#[test]
fn same_interned_instance() {
let a = Interned("A");
let b = a;
assert_eq!(a, b);
let hash_a = FixedHasher.hash_one(a);
let hash_b = FixedHasher.hash_one(b);
assert_eq!(hash_a, hash_b);
}
#[test]
fn same_interned_content() {
let a = Interned::<str>(Box::leak(Box::new("A".to_string())));
let b = Interned::<str>(Box::leak(Box::new("A".to_string())));
assert_ne!(a, b);
}
#[test]
fn different_interned_content() {
let a = Interned::<str>("A");
let b = Interned::<str>("B");
assert_ne!(a, b);
}
}