magi_codex/
set.rs

1use dashmap::DashMap;
2use std::any::{Any, TypeId};
3use std::sync::Arc;
4
5/// Type-indexed set storage - one value per type.
6///
7/// `CodexSet` stores exactly one value per type `T`, similar to how a `HashSet`
8/// stores unique values, but indexed by `TypeId` instead of value.
9///
10/// Useful for global configuration, shared state, or singleton resources.
11///
12/// ## Cloning
13///
14/// `CodexSet` is cheaply cloneable via `Arc`. All clones share the same storage:
15///
16/// ```
17/// use magi_codex::CodexSet;
18///
19/// let set = CodexSet::new();
20/// set.insert(42u32);
21///
22/// let set2 = set.clone();
23/// assert_eq!(set2.get_cloned::<u32>(), Some(42));
24/// ```
25#[derive(Clone)]
26pub struct CodexSet {
27    map: Arc<DashMap<TypeId, Box<dyn Any + Send + Sync>>>,
28}
29
30impl Default for CodexSet {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl CodexSet {
37    /// Create a new empty set.
38    #[inline]
39    pub fn new() -> Self {
40        Self {
41            map: Arc::new(DashMap::new()),
42        }
43    }
44
45    /// Insert or replace a value.
46    ///
47    /// # Example
48    /// ```
49    /// use magi_codex::CodexSet;
50    ///
51    /// struct Config { debug: bool }
52    ///
53    /// let set = CodexSet::new();
54    /// set.insert(Config { debug: true });
55    /// ```
56    #[inline]
57    pub fn insert<T>(&self, value: T)
58    where
59        T: Send + Sync + 'static,
60    {
61        self.map.insert(TypeId::of::<T>(), Box::new(value));
62    }
63
64    /// Returns true if a value of type `T` exists.
65    #[inline]
66    pub fn contains<T>(&self) -> bool
67    where
68        T: Send + Sync + 'static,
69    {
70        self.map.contains_key(&TypeId::of::<T>())
71    }
72
73    /// Access a value immutably via a closure.
74    ///
75    /// The closure runs while holding the lock on the value.
76    ///
77    /// # Example
78    /// ```
79    /// use magi_codex::CodexSet;
80    ///
81    /// struct Config { debug: bool }
82    ///
83    /// let set = CodexSet::new();
84    /// set.insert(Config { debug: true });
85    ///
86    /// let debug = set.with::<Config, _, _>(|config| config.debug).unwrap();
87    /// assert_eq!(debug, true);
88    /// ```
89    pub fn with<T, F, R>(&self, f: F) -> Option<R>
90    where
91        T: Send + Sync + 'static,
92        F: FnOnce(&T) -> R,
93    {
94        let entry = self.map.get(&TypeId::of::<T>())?;
95        let value = entry.value().as_ref().downcast_ref::<T>()?;
96        Some(f(value))
97    }
98
99    /// Access a value mutably via a closure.
100    ///
101    /// The closure runs while holding the lock on the value.
102    ///
103    /// # Example
104    /// ```
105    /// use magi_codex::CodexSet;
106    ///
107    /// struct Config { debug: bool }
108    ///
109    /// let set = CodexSet::new();
110    /// set.insert(Config { debug: false });
111    ///
112    /// set.with_mut::<Config, _, _>(|config| {
113    ///     config.debug = true;
114    /// });
115    ///
116    /// let debug = set.with::<Config, _, _>(|config| config.debug).unwrap();
117    /// assert_eq!(debug, true);
118    /// ```
119    pub fn with_mut<T, F, R>(&self, f: F) -> Option<R>
120    where
121        T: Send + Sync + 'static,
122        F: FnOnce(&mut T) -> R,
123    {
124        let mut entry = self.map.get_mut(&TypeId::of::<T>())?;
125        let value = entry.value_mut().as_mut().downcast_mut::<T>()?;
126        Some(f(value))
127    }
128
129    /// Clone and return a value.
130    ///
131    /// # Example
132    /// ```
133    /// use magi_codex::CodexSet;
134    ///
135    /// let set = CodexSet::new();
136    /// set.insert(42u32);
137    ///
138    /// assert_eq!(set.get_cloned::<u32>(), Some(42));
139    /// ```
140    pub fn get_cloned<T>(&self) -> Option<T>
141    where
142        T: Clone + Send + Sync + 'static,
143    {
144        self.with::<T, _, _>(|v| v.clone())
145    }
146
147    /// Remove and return a value.
148    ///
149    /// # Example
150    /// ```
151    /// use magi_codex::CodexSet;
152    ///
153    /// let set = CodexSet::new();
154    /// set.insert(42u32);
155    ///
156    /// assert_eq!(set.take::<u32>(), Some(42));
157    /// assert!(!set.contains::<u32>());
158    /// ```
159    pub fn take<T>(&self) -> Option<T>
160    where
161        T: Send + Sync + 'static,
162    {
163        self.map
164            .remove(&TypeId::of::<T>())
165            .and_then(|(_, boxed)| boxed.downcast::<T>().ok())
166            .map(|boxed| *boxed)
167    }
168}
169
170// ============================================================================
171// Tests
172// ============================================================================
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[derive(Clone, Debug, PartialEq)]
179    struct Config {
180        debug: bool,
181    }
182
183    #[test]
184    fn basic_operations() {
185        let set = CodexSet::new();
186
187        set.insert(Config { debug: true });
188        assert!(set.contains::<Config>());
189
190        let debug = set.with::<Config, _, _>(|config| config.debug).unwrap();
191        assert_eq!(debug, true);
192    }
193
194    #[test]
195    fn with_mut() {
196        let set = CodexSet::new();
197        set.insert(Config { debug: false });
198
199        set.with_mut::<Config, _, _>(|config| {
200            config.debug = true;
201        });
202
203        let debug = set.with::<Config, _, _>(|config| config.debug).unwrap();
204        assert_eq!(debug, true);
205    }
206
207    #[test]
208    fn get_cloned() {
209        let set = CodexSet::new();
210        set.insert(Config { debug: true });
211
212        let config = set.get_cloned::<Config>().unwrap();
213        assert_eq!(config.debug, true);
214
215        // Original still exists
216        assert!(set.contains::<Config>());
217    }
218
219    #[test]
220    fn take() {
221        let set = CodexSet::new();
222        set.insert(Config { debug: true });
223
224        let config = set.take::<Config>().unwrap();
225        assert_eq!(config.debug, true);
226        assert!(!set.contains::<Config>());
227    }
228
229    #[test]
230    fn clone_shares_storage() {
231        let set = CodexSet::new();
232        set.insert(Config { debug: true });
233
234        let set2 = set.clone();
235
236        // Both see the same value
237        assert!(set2.contains::<Config>());
238        let debug = set2.with::<Config, _, _>(|c| c.debug).unwrap();
239        assert_eq!(debug, true);
240
241        // Mutation via set2 is visible in set
242        set2.with_mut::<Config, _, _>(|config| {
243            config.debug = false;
244        });
245
246        let debug = set.with::<Config, _, _>(|c| c.debug).unwrap();
247        assert_eq!(debug, false);
248    }
249
250    #[tokio::test]
251    async fn clone_across_tasks() {
252        let set = CodexSet::new();
253        set.insert(Config { debug: true });
254
255        let set_clone = set.clone();
256
257        let handle = tokio::spawn(async move {
258            set_clone.insert(42u32);
259            set_clone.with::<Config, _, _>(|c| c.debug).unwrap()
260        });
261
262        let debug = handle.await.unwrap();
263        assert_eq!(debug, true);
264
265        // Original sees value inserted by task
266        assert_eq!(set.get_cloned::<u32>(), Some(42));
267    }
268
269    #[test]
270    fn missing_resource_returns_none() {
271        let set = CodexSet::new();
272
273        let result = set.with::<Config, _, _>(|c| c.debug);
274        assert!(result.is_none());
275    }
276
277    #[test]
278    fn multiple_types() {
279        let set = CodexSet::new();
280
281        set.insert(Config { debug: true });
282        set.insert(42u32);
283        set.insert("hello".to_string());
284
285        assert!(set.contains::<Config>());
286        assert!(set.contains::<u32>());
287        assert!(set.contains::<String>());
288
289        let debug = set.with::<Config, _, _>(|c| c.debug).unwrap();
290        assert_eq!(debug, true);
291
292        let num = set.get_cloned::<u32>().unwrap();
293        assert_eq!(num, 42);
294
295        let s = set.get_cloned::<String>().unwrap();
296        assert_eq!(s, "hello");
297    }
298}