use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
use std::sync::OnceLock;
use arc_swap::ArcSwap;
type Slots = HashMap<TypeId, &'static (dyn Any + Send + Sync), BuildHasherDefault<TypeIdHasher>>;
#[derive(Default)]
struct TypeIdHasher {
hash: u64,
}
impl Hasher for TypeIdHasher {
fn write(&mut self, bytes: &[u8]) {
for chunk in bytes.chunks(8) {
let mut buffer = [0u8; 8];
buffer[..chunk.len()].copy_from_slice(chunk);
self.hash ^= u64::from_ne_bytes(buffer);
}
}
fn write_u64(&mut self, value: u64) {
self.hash ^= value;
}
fn write_u128(&mut self, value: u128) {
self.hash ^= (value as u64) ^ ((value >> 64) as u64);
}
fn finish(&self) -> u64 {
self.hash
}
}
#[derive(Default)]
pub struct Registry {
entries: OnceLock<ArcSwap<Slots>>,
}
impl Registry {
#[must_use]
pub const fn new() -> Self {
Self {
entries: OnceLock::new(),
}
}
pub fn entry<K, V>(&self) -> &'static V
where
K: 'static,
V: Default + Send + Sync + 'static,
{
let entries = self
.entries
.get_or_init(|| ArcSwap::from_pointee(Slots::default()));
let key = TypeId::of::<K>();
if let Some(found) = entries.load().get(&key) {
return downcast(*found);
}
let leaked: &'static V = Box::leak(Box::new(V::default()));
let mut installed: Option<&'static (dyn Any + Send + Sync)> = None;
entries.rcu(|current| {
let mut next = Slots::clone(current);
installed = Some(*next.entry(key).or_insert(leaked));
next
});
downcast(installed.expect("`rcu` runs its closure at least once"))
}
}
fn downcast<V: 'static>(slot: &'static (dyn Any + Send + Sync)) -> &'static V {
slot.downcast_ref()
.expect("a registry slot holds exactly one type")
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
#[derive(Default)]
struct Slot(AtomicUsize);
fn registry() -> &'static Registry {
static REGISTRY: Registry = Registry::new();
®ISTRY
}
#[test]
fn the_same_key_always_returns_the_same_slot() {
let first = registry().entry::<u8, Slot>();
first.0.store(7, Ordering::SeqCst);
let second = registry().entry::<u8, Slot>();
assert!(std::ptr::eq(first, second));
assert_eq!(second.0.load(Ordering::SeqCst), 7);
}
#[test]
fn different_keys_get_different_slots() {
let one = registry().entry::<u16, Slot>();
let two = registry().entry::<u32, Slot>();
assert!(!std::ptr::eq(one, two));
}
#[test]
fn concurrent_first_use_hands_out_one_slot() {
struct Contended;
let handles: Vec<_> = (0..8)
.map(|_| {
thread::spawn(|| {
registry().entry::<Contended, Slot>() as *const Slot as usize
})
})
.collect();
let slots: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(
slots.windows(2).all(|pair| pair[0] == pair[1]),
"every thread must see the same slot"
);
}
}