laterite_core/
registry.rs1use std::any::{Any, TypeId};
17use std::collections::HashMap;
18
19use crate::module::ModuleId;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum ContributeMode {
24 #[default]
26 Append,
27 Replace,
31}
32
33#[derive(Debug, Clone, Copy, Default)]
35pub struct ContributeOpts {
36 pub priority: i32,
38 pub mode: ContributeMode,
39}
40
41struct Entry {
42 owner: ModuleId,
43 priority: i32,
44 mode: ContributeMode,
45 item: Box<dyn Any + Send + Sync>,
46}
47
48pub struct Contribution<'a, T> {
50 pub owner: ModuleId,
51 pub priority: i32,
52 pub mode: ContributeMode,
53 pub item: &'a T,
54}
55
56#[derive(Default)]
58pub struct Registry {
59 current: Option<ModuleId>,
60 by_type: HashMap<TypeId, Vec<Entry>>,
61}
62
63impl Registry {
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn set_owner(&mut self, owner: ModuleId) {
72 self.current = Some(owner);
73 }
74
75 pub fn add<T: Send + Sync + 'static>(&mut self, item: T) {
77 self.add_with(item, ContributeOpts::default());
78 }
79
80 pub fn add_with<T: Send + Sync + 'static>(&mut self, item: T, opts: ContributeOpts) {
85 let owner = self
86 .current
87 .expect("Registry::add called outside a module register() pass");
88 self.by_type
89 .entry(TypeId::of::<T>())
90 .or_default()
91 .push(Entry {
92 owner,
93 priority: opts.priority,
94 mode: opts.mode,
95 item: Box::new(item),
96 });
97 }
98
99 pub fn contributions<T: 'static>(&self) -> Vec<Contribution<'_, T>> {
102 let Some(entries) = self.by_type.get(&TypeId::of::<T>()) else {
103 return Vec::new();
104 };
105 let start = entries
106 .iter()
107 .rposition(|e| e.mode == ContributeMode::Replace)
108 .unwrap_or(0);
109 let mut resolved: Vec<&Entry> = entries[start..].iter().collect();
110 resolved.sort_by_key(|a| std::cmp::Reverse(a.priority));
112 resolved
113 .into_iter()
114 .map(|e| Contribution {
115 owner: e.owner,
116 priority: e.priority,
117 mode: e.mode,
118 item: e
119 .item
120 .downcast_ref::<T>()
121 .expect("keyed by TypeId::of::<T>"),
122 })
123 .collect()
124 }
125
126 pub fn items<T: 'static>(&self) -> Vec<&T> {
128 self.contributions::<T>()
129 .into_iter()
130 .map(|c| c.item)
131 .collect()
132 }
133
134 pub fn take_owned<T: 'static>(&mut self) -> Vec<(ModuleId, T)> {
140 let owners: Vec<ModuleId> = self.contributions::<T>().iter().map(|c| c.owner).collect();
141 owners.into_iter().zip(self.take::<T>()).collect()
142 }
143
144 pub fn take<T: 'static>(&mut self) -> Vec<T> {
145 let Some(entries) = self.by_type.remove(&TypeId::of::<T>()) else {
146 return Vec::new();
147 };
148 let start = entries
149 .iter()
150 .rposition(|e| e.mode == ContributeMode::Replace)
151 .unwrap_or(0);
152 let mut kept: Vec<Entry> = entries.into_iter().skip(start).collect();
153 kept.sort_by_key(|a| std::cmp::Reverse(a.priority));
155 kept.into_iter()
156 .map(|e| *e.item.downcast::<T>().expect("keyed by TypeId::of::<T>"))
157 .collect()
158 }
159
160 pub fn purge_owner(&mut self, owner: ModuleId) {
164 for entries in self.by_type.values_mut() {
165 entries.retain(|e| e.owner != owner);
166 }
167 self.by_type.retain(|_, entries| !entries.is_empty());
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 const A: ModuleId = ModuleId::new("acme.a");
176 const B: ModuleId = ModuleId::new("acme.b");
177
178 #[derive(Debug, PartialEq)]
179 struct Widget(&'static str);
180
181 #[test]
182 fn collects_in_order_and_stamps_owner() {
183 let mut r = Registry::new();
184 r.set_owner(A);
185 r.add(Widget("one"));
186 r.set_owner(B);
187 r.add(Widget("two"));
188
189 let cs = r.contributions::<Widget>();
190 assert_eq!(cs.len(), 2);
191 assert_eq!(cs[0].item, &Widget("one"));
192 assert_eq!(cs[0].owner, A);
193 assert_eq!(cs[1].owner, B);
194 assert_eq!(r.items::<Widget>(), vec![&Widget("one"), &Widget("two")]);
195 }
196
197 #[test]
198 fn higher_priority_sorts_first_ties_keep_order() {
199 let mut r = Registry::new();
200 r.set_owner(A);
201 r.add(Widget("low"));
202 r.add_with(
203 Widget("high"),
204 ContributeOpts {
205 priority: 10,
206 mode: ContributeMode::Append,
207 },
208 );
209 r.add(Widget("low2"));
210 assert_eq!(
211 r.items::<Widget>(),
212 vec![&Widget("high"), &Widget("low"), &Widget("low2")]
213 );
214 }
215
216 #[test]
217 fn replace_supersedes_earlier() {
218 let mut r = Registry::new();
219 r.set_owner(A);
220 r.add(Widget("default"));
221 r.set_owner(B);
222 r.add_with(
223 Widget("override"),
224 ContributeOpts {
225 priority: 0,
226 mode: ContributeMode::Replace,
227 },
228 );
229 assert_eq!(r.items::<Widget>(), vec![&Widget("override")]);
230 }
231
232 #[test]
233 fn purge_owner_drops_only_that_owners_contributions() {
234 let mut r = Registry::new();
235 r.set_owner(A);
236 r.add(Widget("a"));
237 r.add(7u32);
238 r.set_owner(B);
239 r.add(Widget("b"));
240
241 r.purge_owner(A);
242 assert_eq!(r.items::<Widget>(), vec![&Widget("b")]);
244 assert!(r.items::<u32>().is_empty());
245 }
246
247 #[test]
248 fn distinct_types_are_independent() {
249 let mut r = Registry::new();
250 r.set_owner(A);
251 r.add(Widget("w"));
252 r.add(7u32);
253 assert_eq!(r.items::<Widget>(), vec![&Widget("w")]);
254 assert_eq!(r.items::<u32>(), vec![&7u32]);
255 }
256}