use core::panic;
use std::{
any::{Any, TypeId},
cell::RefCell,
collections::HashMap,
marker::PhantomData,
sync::RwLock,
};
use lazy_static::lazy_static;
macro_rules! thread_deadlock {
() => {
panic!("Thread deadlock!")
};
}
lazy_static! {
static ref _TABLE: RwLock<HashMap<TypeId, RwLock<HashMap<String, RwLock<Box<dyn Any + Send + Sync>>>>>> =
RwLock::new(HashMap::new());
}
thread_local! {
static _LOCAL_TABLE: RefCell<HashMap<TypeId, HashMap<String, Box<dyn Any>>>> =
RefCell::new(HashMap::new());
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Context {
With(String, TypeId),
Apply(String, TypeId),
}
enum Lock {
Global,
Type,
Key,
}
thread_local! {
static CONTEXT: RefCell<Vec<Context>> = RefCell::new(Vec::new());
}
struct ContextOperator;
impl ContextOperator {
fn push(ctx: Context) {
CONTEXT.with(|ctx_cell| {
ctx_cell.borrow_mut().push(ctx);
});
}
fn pop() {
CONTEXT.with(|ctx_cell| ctx_cell.borrow_mut().pop());
}
fn cannot_lock_write_lock<T: 'static>(name: &str, lock: Lock) -> bool {
match lock {
Lock::Global => CONTEXT.with_borrow(|v| !v.is_empty()),
Lock::Type => CONTEXT.with_borrow(|v| {
v.iter().any(|x| match x {
Context::With(_, type_id) | Context::Apply(_, type_id) => {
type_id == &TypeId::of::<T>()
}
})
}),
Lock::Key => CONTEXT.with_borrow(|v| {
v.iter().any(|x| match x {
Context::With(key, type_id) | Context::Apply(key, type_id) => {
key == name && type_id == &TypeId::of::<T>()
}
})
}),
}
}
}
fn check_write_deadlock<T: 'static>(name: &str, lock: Lock) {
if ContextOperator::cannot_lock_write_lock::<T>(name, lock) {
thread_deadlock!();
}
}
fn check_read_deadlock<T: 'static>(name: &str) {
if CONTEXT.with_borrow(|v| {
v.iter().any(|x| match x {
Context::Apply(s, type_id) => s == name && type_id == &TypeId::of::<T>(),
_ => false,
})
}) {
thread_deadlock!();
}
}
#[cfg(debug_assertions)]
macro_rules! check_deadlock {
(mut $type:ty : $name:expr ; $em:expr) => {
$crate::check_write_deadlock::<$type>($name, $em);
};
(ref $type:ty : $name:expr) => {
$crate::check_read_deadlock::<$type>($name);
};
}
#[cfg(not(debug_assertions))]
macro_rules! check_deadlock {
(mut $type:ty : $name:expr ; $em:expr) => {};
(ref $type:ty : $name:expr) => {};
}
pub struct Registry<T> {
_marker: PhantomData<T>,
}
impl<T: 'static + Send + Sync + Any> Registry<T> {
fn _register(name: &str, value: T) -> Option<()> {
let type_id = TypeId::of::<T>();
let has_type = {
let map = _TABLE.read().ok()?;
map.contains_key(&type_id)
};
if !has_type {
check_deadlock!(mut T:name;Lock::Global);
let mut map = _TABLE.write().ok()?;
map.insert(type_id, RwLock::new(HashMap::new()));
}
let map = _TABLE.read().ok()?;
check_deadlock!(mut T:name;Lock::Type);
let mut type_map = map.get(&type_id)?.write().ok()?;
type_map.insert(String::from(name), RwLock::new(Box::new(value)));
Some(())
}
pub fn register(name: &str, value: T) -> Result<(), ()> {
Self::_register(name, value).ok_or(())
}
pub fn remove(name: &str) -> Option<T> {
let type_id = TypeId::of::<T>();
let lock_value = {
let map = _TABLE.read().ok()?;
let type_map = map.get(&type_id)?;
check_deadlock!(mut T:name;Lock::Type);
let mut type_map = type_map.write().ok()?;
type_map.remove(name)?
};
let value = lock_value.into_inner().ok()?;
let type_value = value.downcast::<T>().ok()?;
Some(*type_value)
}
fn _exists(name: &str) -> Option<bool> {
let type_id = TypeId::of::<T>();
let map = _TABLE.read().ok()?;
let lock_type_map = map.get(&type_id)?;
let type_map = lock_type_map.read().ok()?;
Some(type_map.contains_key(name))
}
pub fn exists(name: &str) -> bool {
Self::_exists(name).unwrap_or(false)
}
pub fn apply<R, F: FnOnce(&mut T) -> R>(name: &str, func: F) -> Option<R> {
let type_id = TypeId::of::<T>();
let type_map = _TABLE.read().ok()?;
let type_map = type_map.get(&type_id)?.read().ok()?;
check_deadlock!(mut T:name;Lock::Key);
let mut value = type_map.get(name)?.write().ok()?;
let var = value.downcast_mut::<T>()?;
ContextOperator::push(Context::Apply(String::from(name), type_id));
let ret = Some(func(var));
ContextOperator::pop();
ret
}
pub fn with<R, F: FnOnce(&T) -> R>(name: &str, func: F) -> Option<R> {
let type_id = TypeId::of::<T>();
let type_map = _TABLE.read().ok()?;
let type_map = type_map.get(&type_id)?.read().ok()?;
check_deadlock!(ref T:name);
let value = type_map.get(name)?.read().ok()?;
let var = value.downcast_ref::<T>()?;
ContextOperator::push(Context::With(String::from(name), type_id));
let ret = Some(func(var));
ContextOperator::pop();
ret
}
pub fn replace(name: &str, value: T) -> Option<T> {
let type_id = TypeId::of::<T>();
let type_map = _TABLE.read().ok()?;
let type_map = type_map.get(&type_id)?;
let value = {
check_deadlock!(mut T:name;Lock::Type);
let mut type_map = type_map.write().ok()?;
let ret = type_map.remove(name)?;
type_map.insert(String::from(name), RwLock::new(Box::new(value)));
ret
};
let value = value.into_inner().ok()?;
let type_value = value.downcast::<T>().ok()?;
Some(*type_value)
}
#[deprecated(since = "0.1.6", note = "use `replace` instead")]
pub fn take(name: &str, value: T) -> Option<T> {
Self::replace(name, value)
}
}
pub struct LocalRegistry<T> {
_marker: PhantomData<T>,
}
impl<T: 'static> LocalRegistry<T> {
pub fn register(name: &str, value: T) {
let type_id = TypeId::of::<T>();
let has_type = _LOCAL_TABLE.with_borrow(|table| table.contains_key(&type_id));
if !has_type {
_LOCAL_TABLE.with_borrow_mut(|table| {
table.insert(type_id, HashMap::new());
});
}
_LOCAL_TABLE.with_borrow_mut(|table| {
let type_map = table.get_mut(&type_id).unwrap();
type_map.insert(String::from(name), Box::new(value));
})
}
pub fn remove(name: &str) -> Option<T> {
let type_id = TypeId::of::<T>();
let value = _LOCAL_TABLE.with_borrow_mut(|table| {
let type_map = table.get_mut(&type_id)?;
type_map.remove(name)
})?;
let value = value.downcast::<T>().ok()?;
Some(*value)
}
pub fn exists(name: &str) -> bool {
let type_id = TypeId::of::<T>();
_LOCAL_TABLE.with_borrow(|table| {
let type_map = table.get(&type_id).unwrap();
type_map.contains_key(name)
})
}
pub fn apply<R, F: FnOnce(&mut T) -> R>(name: &str, func: F) -> Option<R> {
let type_id = TypeId::of::<T>();
_LOCAL_TABLE.with_borrow_mut(|table| {
let type_map = table.get_mut(&type_id)?;
let value = type_map.get_mut(name)?;
let value = value.downcast_mut::<T>()?;
Some(func(value))
})
}
pub fn with<R, F: FnOnce(&T) -> R>(name: &str, func: F) -> Option<R> {
let type_id = TypeId::of::<T>();
_LOCAL_TABLE.with_borrow(|table| {
let type_map = table.get(&type_id)?;
let value = type_map.get(name)?;
let value = value.downcast_ref::<T>()?;
Some(func(value))
})
}
pub fn replace(name: &str, value: T) -> Option<T> {
let type_id = TypeId::of::<T>();
let value = _LOCAL_TABLE.with_borrow_mut(|table| {
let type_map = table.get_mut(&type_id)?;
type_map.insert(name.to_string(), Box::new(value))
})?;
let value = value.downcast::<T>().ok()?;
Some(*value)
}
}
#[macro_export]
macro_rules! id {
($($x:ident).+) => {
concat!($('.', stringify!($x)),+)
};
(@ $root:ident . $($x:ident).+) => {
constcat::concat!($root, concat!($('.', stringify!($x)),+))
}
}