use std::{any::TypeId, collections::HashMap};
pub trait Loggable {
fn as_ptr(&self) -> *const () {
self as *const Self as *const ()
}
fn type_id(&self) -> TypeId;
}
pub fn downcast_ref<'a, T: 'static>(loggable: &dyn Loggable) -> Option<&'a T> {
if loggable.type_id() == TypeId::of::<T>() {
let ptr = loggable.as_ptr();
Some(unsafe { &*(ptr as *const T) })
} else {
None
}
}
pub type LogFnType = dyn Fn(&dyn Loggable) + Sync + Send;
pub type LogFn = Box<LogFnType>;
use std::sync::LazyLock;
static LOG_MAP: LazyLock<std::sync::RwLock<HashMap<TypeId, Vec<LogFn>>>> =
LazyLock::new(|| std::sync::RwLock::new(HashMap::new()));
static LOG_VEC: std::sync::RwLock<Vec<LogFn>> = std::sync::RwLock::new(Vec::new());
pub fn log_any<T: Loggable>(loggable: &T) {
{
let Ok(log_map) = LOG_MAP.read() else {
return;
};
let Some(log_functions) = log_map.get(&loggable.type_id()) else {
return;
};
for log_function in log_functions {
log_function(loggable);
}
}
let Ok(log_vec) = LOG_VEC.read() else {
return;
};
for log_function in log_vec.iter() {
log_function(loggable);
}
}
pub fn push_log_impl<T: Loggable + 'static>(log_fn: impl Fn(&T) + Sync + Send + 'static) {
let mut log_map = match LOG_MAP.write() {
Ok(lock) => lock,
Err(_) => return,
};
let wrapper = move |loggable: &dyn Loggable| {
if let Some(concrete) = downcast_ref::<T>(loggable) {
log_fn(concrete);
}
};
match log_map.entry(TypeId::of::<T>()) {
std::collections::hash_map::Entry::Occupied(mut occupied_entry) => {
occupied_entry.get_mut().push(Box::new(wrapper));
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
vacant_entry.insert(vec![Box::new(wrapper)]);
}
};
}
pub fn push_log_any(log_fn: impl Fn(&dyn Loggable) + Sync + Send + 'static) {
let mut log_vec = match LOG_VEC.write() {
Ok(lock) => lock,
Err(_) => return,
};
log_vec.push(Box::new(log_fn));
}