Skip to main content

quark/
lib.rs

1//! Generic link-time registry infrastructure for Rust.
2//!
3//! Provides a generic `Registrable` trait, `Registry<T>` struct, and
4//! `define_registry!` macro for building type-safe, owned registries
5//! backed by [`inventory`] link-time collection.
6
7pub use inventory;
8
9use std::collections::HashMap;
10
11/// Trait for types that can be stored in a `Registry`.
12///
13/// Implementors must provide a string key used for lookup and deduplication.
14/// The `'static + Send + Sync` bounds are required because descriptors live
15/// in static memory (via `inventory`) and registries are shared across threads.
16pub trait Registrable: 'static + Send + Sync {
17    /// Return the unique lookup key used by the registry.
18    fn registry_key(&self) -> &str;
19}
20
21/// A generic, owned registry mapping string keys to `&'static T` references.
22///
23/// Registries are immutable after construction — no `Mutex` needed.
24/// Use `auto_discover()` to populate from `inventory`, or `new()` + `register()`
25/// for manual construction (useful in tests).
26pub struct Registry<T: Registrable> {
27    items: HashMap<String, &'static T>,
28}
29
30impl<T: Registrable> std::fmt::Debug for Registry<T> {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("Registry")
33            .field("len", &self.items.len())
34            .field("keys", &self.list())
35            .finish()
36    }
37}
38
39impl<T: Registrable> Registry<T> {
40    /// Create an empty registry.
41    pub fn new() -> Self {
42        Self {
43            items: HashMap::new(),
44        }
45    }
46
47    /// Populate from all `inventory::submit!`-ed items of type `T`.
48    pub fn auto_discover() -> Self
49    where
50        T: inventory::Collect,
51    {
52        let mut reg = Self::new();
53        for item in inventory::iter::<T> {
54            reg.register(item);
55        }
56        reg
57    }
58
59    /// Insert a descriptor. If a descriptor with the same key already exists,
60    /// it is replaced (last-write-wins).
61    pub fn register(&mut self, item: &'static T) {
62        self.items.insert(item.registry_key().to_string(), item);
63    }
64
65    /// Look up an item by registry key.
66    pub fn get(&self, key: &str) -> Option<&'static T> {
67        self.items.get(key).copied()
68    }
69
70    /// Returns all registered keys in arbitrary order.
71    pub fn list(&self) -> Vec<&str> {
72        self.items.keys().map(|k| k.as_str()).collect()
73    }
74
75    /// Return the number of registered items.
76    pub fn len(&self) -> usize {
77        self.items.len()
78    }
79
80    /// Return `true` when no items are registered.
81    pub fn is_empty(&self) -> bool {
82        self.items.is_empty()
83    }
84
85    /// Iterates over all registered descriptors in arbitrary order.
86    pub fn iter(&self) -> impl Iterator<Item = &'static T> + '_ {
87        self.items.values().copied()
88    }
89}
90
91impl<T: Registrable> Default for Registry<T> {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl<T: Registrable> Clone for Registry<T> {
98    fn clone(&self) -> Self {
99        Self {
100            items: self.items.clone(),
101        }
102    }
103}
104
105/// Generates a newtype wrapper around `Registry<T>` with standard impls.
106///
107/// # Usage
108///
109/// ```ignore
110/// quark::define_registry! {
111///     pub struct ScriptRegistry for ScriptDescriptor;
112/// }
113/// ```
114///
115/// This generates:
116/// - A newtype struct wrapping `quark::Registry<T>`
117/// - `Deref` / `DerefMut` to `Registry<T>` (inherits `get`, `list`, `len`, etc.)
118/// - `new()`, `auto_discover()`, `register()` on the newtype
119/// - `Debug`, `Clone`, `Default` impls
120#[macro_export]
121macro_rules! define_registry {
122    (
123        $(#[$meta:meta])*
124        $vis:vis struct $Name:ident for $Item:ty;
125    ) => {
126        $(#[$meta])*
127        $vis struct $Name {
128            inner: $crate::Registry<$Item>,
129        }
130
131        impl $Name {
132            pub fn new() -> Self {
133                Self {
134                    inner: $crate::Registry::new(),
135                }
136            }
137
138            pub fn auto_discover() -> Self {
139                Self {
140                    inner: $crate::Registry::auto_discover(),
141                }
142            }
143
144            pub fn register(&mut self, item: &'static $Item) {
145                self.inner.register(item);
146            }
147        }
148
149        impl ::std::ops::Deref for $Name {
150            type Target = $crate::Registry<$Item>;
151
152            fn deref(&self) -> &Self::Target {
153                &self.inner
154            }
155        }
156
157        impl ::std::ops::DerefMut for $Name {
158            fn deref_mut(&mut self) -> &mut Self::Target {
159                &mut self.inner
160            }
161        }
162
163        impl ::std::fmt::Debug for $Name {
164            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
165                f.debug_struct(stringify!($Name))
166                    .field("len", &self.inner.len())
167                    .field("keys", &self.inner.list())
168                    .finish()
169            }
170        }
171
172        impl Clone for $Name {
173            fn clone(&self) -> Self {
174                Self {
175                    inner: self.inner.clone(),
176                }
177            }
178        }
179
180        impl Default for $Name {
181            fn default() -> Self {
182                Self::new()
183            }
184        }
185    };
186}