use std::any::TypeId;
use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use crate::export::NativeClass;
use crate::init::InitLevel;
static CLASS_REGISTRY: Lazy<RwLock<HashMap<TypeId, ClassInfo>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
#[derive(Clone, Debug)]
pub(crate) struct ClassInfo {
pub name: Cow<'static, str>,
pub init_level: InitLevel,
}
#[inline]
pub(crate) fn with_class_info<C: NativeClass, F, R>(f: F) -> Option<R>
where
F: FnOnce(&ClassInfo) -> R,
{
CLASS_REGISTRY.read().get(&TypeId::of::<C>()).map(f)
}
#[inline]
pub(crate) fn class_name<C: NativeClass>() -> Option<Cow<'static, str>> {
with_class_info::<C, _, _>(|i| i.name.clone())
}
#[inline]
pub(crate) fn class_name_or_default<C: NativeClass>() -> Cow<'static, str> {
class_name::<C>().unwrap_or_else(|| Cow::Borrowed(std::any::type_name::<C>()))
}
#[inline]
pub(crate) fn register_class_as<C: NativeClass>(
name: Cow<'static, str>,
init_level: InitLevel,
) -> Result<bool, RegisterError> {
let type_id = TypeId::of::<C>();
let mut registry = CLASS_REGISTRY.write();
match registry.entry(type_id) {
Entry::Vacant(entry) => {
entry.insert(ClassInfo { name, init_level });
Ok(true)
}
Entry::Occupied(entry) => {
let class_info = entry.get();
let kind = if class_info.name != name {
Some(RegisterErrorKind::ConflictingName)
} else if class_info.init_level.intersects(init_level) {
Some(RegisterErrorKind::AlreadyOnSameLevel)
} else {
None
};
if let Some(kind) = kind {
Err(RegisterError {
class_info: class_info.clone(),
type_name: std::any::type_name::<C>(),
kind,
})
} else {
Ok(false)
}
}
}
}
#[inline]
#[allow(dead_code)] pub(crate) fn types_with_init_level(allow: InitLevel, deny: InitLevel) -> Vec<Cow<'static, str>> {
let registry = CLASS_REGISTRY.read();
let mut list = registry
.values()
.filter_map(|class_info| {
(class_info.init_level.intersects(allow) && !class_info.init_level.intersects(deny))
.then(|| class_info.name.clone())
})
.collect::<Vec<_>>();
list.sort_unstable();
list
}
#[derive(Debug)]
pub(crate) struct RegisterError {
pub type_name: &'static str,
pub class_info: ClassInfo,
pub kind: RegisterErrorKind,
}
#[derive(Debug)]
pub(crate) enum RegisterErrorKind {
ConflictingName,
AlreadyOnSameLevel,
}
impl fmt::Display for RegisterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
RegisterErrorKind::ConflictingName => {
write!(
f,
"`{}` has already been registered as `{}`",
self.type_name, self.class_info.name
)
}
RegisterErrorKind::AlreadyOnSameLevel => {
write!(f, "`{}` has already been registered", self.type_name)
}
}
}
}
#[inline]
pub(crate) fn cleanup() {
CLASS_REGISTRY.write().clear();
}