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>,
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: 'static>(&mut self, item: T) {
77 self.add_with(item, ContributeOpts::default());
78 }
79
80 pub fn add_with<T: 'static>(&mut self, item: T, opts: ContributeOpts) {
82 let owner = self
83 .current
84 .expect("Registry::add called outside a module register() pass");
85 self.by_type
86 .entry(TypeId::of::<T>())
87 .or_default()
88 .push(Entry {
89 owner,
90 priority: opts.priority,
91 mode: opts.mode,
92 item: Box::new(item),
93 });
94 }
95
96 pub fn contributions<T: 'static>(&self) -> Vec<Contribution<'_, T>> {
99 let Some(entries) = self.by_type.get(&TypeId::of::<T>()) else {
100 return Vec::new();
101 };
102 let start = entries
103 .iter()
104 .rposition(|e| e.mode == ContributeMode::Replace)
105 .unwrap_or(0);
106 let mut resolved: Vec<&Entry> = entries[start..].iter().collect();
107 resolved.sort_by_key(|a| std::cmp::Reverse(a.priority));
109 resolved
110 .into_iter()
111 .map(|e| Contribution {
112 owner: e.owner,
113 priority: e.priority,
114 mode: e.mode,
115 item: e
116 .item
117 .downcast_ref::<T>()
118 .expect("keyed by TypeId::of::<T>"),
119 })
120 .collect()
121 }
122
123 pub fn items<T: 'static>(&self) -> Vec<&T> {
125 self.contributions::<T>()
126 .into_iter()
127 .map(|c| c.item)
128 .collect()
129 }
130
131 pub fn take_owned<T: 'static>(&mut self) -> Vec<(ModuleId, T)> {
137 let owners: Vec<ModuleId> = self.contributions::<T>().iter().map(|c| c.owner).collect();
138 owners.into_iter().zip(self.take::<T>()).collect()
139 }
140
141 pub fn take<T: 'static>(&mut self) -> Vec<T> {
142 let Some(entries) = self.by_type.remove(&TypeId::of::<T>()) else {
143 return Vec::new();
144 };
145 let start = entries
146 .iter()
147 .rposition(|e| e.mode == ContributeMode::Replace)
148 .unwrap_or(0);
149 let mut kept: Vec<Entry> = entries.into_iter().skip(start).collect();
150 kept.sort_by_key(|a| std::cmp::Reverse(a.priority));
152 kept.into_iter()
153 .map(|e| *e.item.downcast::<T>().expect("keyed by TypeId::of::<T>"))
154 .collect()
155 }
156
157 pub fn purge_owner(&mut self, owner: ModuleId) {
161 for entries in self.by_type.values_mut() {
162 entries.retain(|e| e.owner != owner);
163 }
164 self.by_type.retain(|_, entries| !entries.is_empty());
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 const A: ModuleId = ModuleId::new("acme.a");
173 const B: ModuleId = ModuleId::new("acme.b");
174
175 #[derive(Debug, PartialEq)]
176 struct Widget(&'static str);
177
178 #[test]
179 fn collects_in_order_and_stamps_owner() {
180 let mut r = Registry::new();
181 r.set_owner(A);
182 r.add(Widget("one"));
183 r.set_owner(B);
184 r.add(Widget("two"));
185
186 let cs = r.contributions::<Widget>();
187 assert_eq!(cs.len(), 2);
188 assert_eq!(cs[0].item, &Widget("one"));
189 assert_eq!(cs[0].owner, A);
190 assert_eq!(cs[1].owner, B);
191 assert_eq!(r.items::<Widget>(), vec![&Widget("one"), &Widget("two")]);
192 }
193
194 #[test]
195 fn higher_priority_sorts_first_ties_keep_order() {
196 let mut r = Registry::new();
197 r.set_owner(A);
198 r.add(Widget("low"));
199 r.add_with(
200 Widget("high"),
201 ContributeOpts {
202 priority: 10,
203 mode: ContributeMode::Append,
204 },
205 );
206 r.add(Widget("low2"));
207 assert_eq!(
208 r.items::<Widget>(),
209 vec![&Widget("high"), &Widget("low"), &Widget("low2")]
210 );
211 }
212
213 #[test]
214 fn replace_supersedes_earlier() {
215 let mut r = Registry::new();
216 r.set_owner(A);
217 r.add(Widget("default"));
218 r.set_owner(B);
219 r.add_with(
220 Widget("override"),
221 ContributeOpts {
222 priority: 0,
223 mode: ContributeMode::Replace,
224 },
225 );
226 assert_eq!(r.items::<Widget>(), vec![&Widget("override")]);
227 }
228
229 #[test]
230 fn purge_owner_drops_only_that_owners_contributions() {
231 let mut r = Registry::new();
232 r.set_owner(A);
233 r.add(Widget("a"));
234 r.add(7u32);
235 r.set_owner(B);
236 r.add(Widget("b"));
237
238 r.purge_owner(A);
239 assert_eq!(r.items::<Widget>(), vec![&Widget("b")]);
241 assert!(r.items::<u32>().is_empty());
242 }
243
244 #[test]
245 fn distinct_types_are_independent() {
246 let mut r = Registry::new();
247 r.set_owner(A);
248 r.add(Widget("w"));
249 r.add(7u32);
250 assert_eq!(r.items::<Widget>(), vec![&Widget("w")]);
251 assert_eq!(r.items::<u32>(), vec![&7u32]);
252 }
253}