gom/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
use std::{
any::{Any, TypeId},
collections::HashMap,
sync::Mutex,
};
use lazy_static::lazy_static;
lazy_static! {
static ref REGISTRY: Mutex<HashMap<TypeId, Box<dyn Any + Send>>> = Mutex::new(HashMap::new());
}
/// Global Object Registry Visitor
///
/// This struct is used to register and access global objects of a specific type.
///
/// # Example
/// ```rust
/// use gom::Registry;
///
/// Registry::register("key", 123);
/// let value = Registry::<i32>::apply("key", |v| *v + 1);
/// assert_eq!(value, Some(124));
/// ```
pub struct Registry<T> {
_use: Option<T>,
}
impl<T: Send + 'static> Registry<T> {
/// Register a new value with the given key
///
/// If the key already exists in the same type, the value will be overwritten.
///
/// # Example
/// ```rust
/// use gom::Registry;
///
/// Registry::register("key", 123);
/// ```
pub fn register(key: &str, value: T) {
let mut registry = REGISTRY.lock().unwrap();
// Check if the map already exists for this type
if !registry.contains_key(&TypeId::of::<T>()) {
let map: HashMap<String, T> = HashMap::new();
registry.insert(TypeId::of::<T>(), Box::new(map));
}
// Get type map
let map = registry
.get_mut(&TypeId::of::<T>())
.unwrap()
.downcast_mut::<HashMap<String, T>>()
.unwrap();
// Insert the value into the map
map.insert(key.to_string(), value);
}
/// Apply a function to the value with the given key
///
/// **If this function is nested, it will cause thread deadlock.**
///
/// If the key does not exist, the function will not be called and `None` will be returned.
///
/// # Returns
/// `Some(R)` if the key exists and the function was applied successfully, `None` otherwise.
///
/// # Example
/// ```rust
/// use gom::Registry;
///
/// Registry::register("key", 123);
/// let value = Registry::<i32>::apply("key", |v| *v + 1);
/// assert_eq!(value, Some(124));
/// ```
pub fn apply<R, F: FnOnce(&mut T) -> R>(key: &str, f: F) -> Option<R> {
let mut registry = REGISTRY.lock().unwrap();
// Get type map
let map = registry
.get_mut(&TypeId::of::<T>())?
.downcast_mut::<HashMap<String, T>>()?;
// Get value
let value = map.get_mut(key)?;
Some(f(value))
}
/// Get the value with the given key and reomve it from the registry
///
/// If the key does not exist, `None` will be returned.
///
/// # Returns
/// `Some(T)` if the key exists, `None` otherwise.
///
/// # Example
/// ```rust
/// use gom::Registry;
///
/// Registry::register("key", 123);
/// let value = Registry::<i32>::remove("key");
/// assert_eq!(value, Some(123));
/// let value = Registry::<i32>::remove("key");
/// assert_eq!(value, None);
/// ```
pub fn remove(key: &str) -> Option<T> {
let mut registry = REGISTRY.lock().unwrap();
// Get type map
let map = registry
.get_mut(&TypeId::of::<T>())?
.downcast_mut::<HashMap<String, T>>()?;
// Get value
map.remove(key)
}
/// Apply a function to the value with the given key
///
/// **This function will not cause thread deadlocks.**
///
/// If the key does not exist, the function will not be called and `None` will be returned.
///
/// In the context of 'with', the value cannot be retrieved again.
///
/// # Returns
/// `Some(R)` if the key exists and the function was applied successfully, `None` otherwise.
///
/// # Example
/// ```rust
/// use gom::Registry;
///
/// Registry::register("key", 123);
/// let value = Registry::<i32>::with("key", |v| *v + 1);
/// assert_eq!(value, Some(124));
/// ```
pub fn with<R, F: FnOnce(&mut T) -> R>(key: &str, f: F) -> Option<R> {
let mut value = Self::remove(key)?;
let result = f(&mut value);
Self::register(key, value);
Some(result)
}
}
/// Make a identifier string with the given path
///
/// ```rust
/// use gom::id;
///
/// const MY_ID: &str = id!(my.module.MyType);
/// const OTHER_ID: &str = id!(@MY_ID.other.OtherType);
///
/// assert_eq!(MY_ID, ".my.module.MyType");
/// assert_eq!(OTHER_ID, ".my.module.MyType.other.OtherType");
/// ```
#[macro_export]
macro_rules! id {
($($x:ident).+) => {
concat!($('.', stringify!($x)),+)
};
(@ $root:ident . $($x:ident).+) => {
{
const ROOT: &str = $root;
const PATH: &str = concat!($('.', stringify!($x)),+);
constcat::concat!(ROOT, PATH)
}
}
}