use crate::ornaments::{GetHash,HashedStr};
use crate::hash_str::{HashStr,SIZE_HASH};
use hashbrown::HashTable;
#[derive(Debug)]
pub struct HashStrHost(bumpalo::Bump);
impl HashStrHost{
#[inline]
pub fn new()->Self{
Self(bumpalo::Bump::new())
}
#[inline]
pub fn with_capacity(capacity:usize)->Self{
Self(bumpalo::Bump::with_capacity(capacity))
}
#[doc(hidden)]
pub unsafe fn clear(&mut self){
self.0.reset();
}
#[inline]
pub fn alloc<'a>(&self,index:impl GetHash+Into<&'a str>)->&HashStr{
self.alloc_str_with_hash(index.get_hash(),index.into())
}
#[inline]
pub(crate) fn alloc_str_with_hash(&self,hash:u64,str:&str)->&HashStr{
let hash_str_len=SIZE_HASH+str.len();
let layout=bumpalo::core_alloc::alloc::Layout::from_size_align(hash_str_len,SIZE_HASH).unwrap();
let new_hash_str_bytes_ptr=self.0.alloc_layout(layout).as_ptr();
let new_hash_str_bytes=unsafe{core::slice::from_raw_parts_mut(
new_hash_str_bytes_ptr,
hash_str_len
)};
new_hash_str_bytes[..SIZE_HASH].copy_from_slice(&hash.to_ne_bytes());
new_hash_str_bytes[SIZE_HASH..].copy_from_slice(str.as_bytes());
unsafe{HashStr::ref_from_bytes_unchecked(new_hash_str_bytes)}
}
}
#[derive(Debug)]
pub struct HashStrCache<'host>{
entries:HashTable<&'host HashStr>,
}
fn get_precomputed_hash(&hash_str:&&HashStr)->u64{
hash_str.precomputed_hash()
}
impl<'host> HashStrCache<'host>{
#[inline]
pub fn new()->HashStrCache<'host>{
HashStrCache{
entries:HashTable::new(),
}
}
#[inline]
pub fn with_capacity(capacity:usize)->HashStrCache<'host>{
HashStrCache{
entries:HashTable::with_capacity(capacity),
}
}
#[inline]
pub fn clear(&mut self){
self.entries.clear();
}
#[inline]
pub fn get<'a>(&self,index:impl GetHash+Into<&'a str>)->Option<&'host HashStr>{
self.presence(index).get()
}
#[inline]
pub fn presence<'a>(&self,index:impl GetHash+Into<&'a str>)->Presence<'a,&'host HashStr>{
self.presence_str_with_hash(index.get_hash(),index.into())
}
#[inline]
pub(crate) fn presence_str_with_hash<'a>(&self,hash:u64,str:&'a str)->Presence<'a,&'host HashStr>{
match self.entries.find(hash,|&s|s.as_str()==str){
Some(entry)=>Presence::Present(entry),
None=>Presence::Absent(HashedStr{hash,str})
}
}
#[inline]
pub fn cache(&mut self,hash_str:&'host HashStr)->&'host HashStr{
let (hash,str)=(hash_str.precomputed_hash(),hash_str.as_str());
self.intern_str_with_hash(||hash_str,hash,str)
}
#[inline]
pub fn intern_with(&mut self,host:&'host HashStrHost,index:impl GetHash+AsRef<str>)->&'host HashStr{
let (hash,str)=(index.get_hash(),index.as_ref());
self.intern_str_with_hash(||host.alloc_str_with_hash(hash,str),hash,str)
}
#[inline]
pub(crate) fn intern_str_with_hash(&mut self,with:impl FnOnce()->&'host HashStr,hash:u64,str:&str)->&'host HashStr{
self.entries.entry(
hash,
|&s|s.as_str()==str,
get_precomputed_hash,
).or_insert_with(with).get()
}
#[inline]
pub fn iter<'a>(&'a self)->impl Iterator<Item=&'host HashStr>+'a{
self.into_iter()
}
#[inline]
pub fn len(&self)->usize{
self.entries.len()
}
#[inline]
pub fn capacity(&self)->usize{
self.entries.capacity()
}
#[inline]
pub fn reserve(&mut self,additional:usize){
self.entries.reserve(additional,get_precomputed_hash)
}
}
impl<'host,'a> IntoIterator for &'a HashStrCache<'host>{
type Item=&'host HashStr;
type IntoIter=core::iter::Copied<hashbrown::hash_table::Iter<'a,&'host HashStr>>;
#[inline]
fn into_iter(self)->Self::IntoIter{
self.entries.iter().copied()
}
}
#[derive(Debug)]
pub enum Presence<'a,T>{
Present(T),
Absent(HashedStr<'a>),
}
impl<'a,T> Presence<'a,T>{
#[inline]
pub fn get(self)->Option<T>{
match self{
Presence::Present(entry)=>Some(entry),
Presence::Absent(_)=>None,
}
}
}
impl<'a,'host> Presence<'a,&'host HashStr>{
#[inline]
pub fn or_present_in<'new>(self,cache:&'a HashStrCache<'new>)->Presence<'a,&'new HashStr> where 'host:'new{
match self{
Presence::Present(entry)=>Presence::Present(entry),
Presence::Absent(HashedStr{hash,str})=>cache.presence_str_with_hash(hash,str),
}
}
#[inline]
pub fn or_intern_with<'new>(self,host:&'new HashStrHost,cache:&mut HashStrCache<'new>)->&'new HashStr where 'host:'new{
match self{
Presence::Present(entry)=>entry,
Presence::Absent(HashedStr{hash,str})=>cache.intern_str_with_hash(||host.alloc_str_with_hash(hash,str),hash,str),
}
}
}
#[test]
fn test_cache(){
let lifetime_host=HashStrHost::new();
let mut words=HashStrCache::new();
let a:&HashStr=words.intern_with(&lifetime_host,"bruh");
let b:&HashStr=words.get("bruh").unwrap();
assert_eq!(a,b);
assert!(core::ptr::addr_eq(a,b));
let a2:&HashStr=words.cache(a);
let b2:&HashStr=words.get(b).unwrap();
assert_eq!(a,a2);
assert_eq!(b,b2);
assert!(core::ptr::addr_eq(a,a2));
assert!(core::ptr::addr_eq(b,b2));
drop(words);
assert_eq!(a,b);
assert_eq!(a,b);
}
#[test]
fn readme(){
use crate::hstr;
use crate::hash::HashStrMap;
use crate::ornaments::UnhashedStr;
let hstr_static:&HashStr=hstr!("bruh");
let hstr_runtime:&HashStr=&HashStr::anonymous("bruh".to_owned());
let lifetime_host=HashStrHost::new();
let mut cache=HashStrCache::new();
let hstr_interned:&HashStr=cache.intern_with(&lifetime_host,"bruh");
let hstr_interned1:&HashStr=cache.intern_with(&lifetime_host,hstr_static);
let hstr_interned2:&HashStr=cache.cache(hstr_runtime);
let hstr_interned3:&HashStr=cache.cache(hstr_interned);
assert!(core::ptr::addr_eq(hstr_interned,hstr_interned1));
assert!(core::ptr::addr_eq(hstr_interned,hstr_interned2));
assert!(core::ptr::addr_eq(hstr_interned,hstr_interned3));
let mut map=HashStrMap::default();
map.insert(hstr_static,1);
assert_eq!(map.get(hstr_static),Some(&1));
assert_eq!(map.get(hstr_runtime),Some(&1));
assert_eq!(map.get(hstr_interned),Some(&1));
assert_eq!(map.get(UnhashedStr::from_ref("bruh")),Some(&1));
drop(cache);
drop(lifetime_host);
}