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<T: 'static>(&mut self) -> Vec<T> {
135 let Some(entries) = self.by_type.remove(&TypeId::of::<T>()) else {
136 return Vec::new();
137 };
138 let start = entries
139 .iter()
140 .rposition(|e| e.mode == ContributeMode::Replace)
141 .unwrap_or(0);
142 let mut kept: Vec<Entry> = entries.into_iter().skip(start).collect();
143 kept.sort_by_key(|a| std::cmp::Reverse(a.priority));
145 kept.into_iter()
146 .map(|e| *e.item.downcast::<T>().expect("keyed by TypeId::of::<T>"))
147 .collect()
148 }
149
150 pub fn purge_owner(&mut self, owner: ModuleId) {
154 for entries in self.by_type.values_mut() {
155 entries.retain(|e| e.owner != owner);
156 }
157 self.by_type.retain(|_, entries| !entries.is_empty());
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 const A: ModuleId = ModuleId::new("acme.a");
166 const B: ModuleId = ModuleId::new("acme.b");
167
168 #[derive(Debug, PartialEq)]
169 struct Widget(&'static str);
170
171 #[test]
172 fn collects_in_order_and_stamps_owner() {
173 let mut r = Registry::new();
174 r.set_owner(A);
175 r.add(Widget("one"));
176 r.set_owner(B);
177 r.add(Widget("two"));
178
179 let cs = r.contributions::<Widget>();
180 assert_eq!(cs.len(), 2);
181 assert_eq!(cs[0].item, &Widget("one"));
182 assert_eq!(cs[0].owner, A);
183 assert_eq!(cs[1].owner, B);
184 assert_eq!(r.items::<Widget>(), vec![&Widget("one"), &Widget("two")]);
185 }
186
187 #[test]
188 fn higher_priority_sorts_first_ties_keep_order() {
189 let mut r = Registry::new();
190 r.set_owner(A);
191 r.add(Widget("low"));
192 r.add_with(
193 Widget("high"),
194 ContributeOpts {
195 priority: 10,
196 mode: ContributeMode::Append,
197 },
198 );
199 r.add(Widget("low2"));
200 assert_eq!(
201 r.items::<Widget>(),
202 vec![&Widget("high"), &Widget("low"), &Widget("low2")]
203 );
204 }
205
206 #[test]
207 fn replace_supersedes_earlier() {
208 let mut r = Registry::new();
209 r.set_owner(A);
210 r.add(Widget("default"));
211 r.set_owner(B);
212 r.add_with(
213 Widget("override"),
214 ContributeOpts {
215 priority: 0,
216 mode: ContributeMode::Replace,
217 },
218 );
219 assert_eq!(r.items::<Widget>(), vec![&Widget("override")]);
220 }
221
222 #[test]
223 fn purge_owner_drops_only_that_owners_contributions() {
224 let mut r = Registry::new();
225 r.set_owner(A);
226 r.add(Widget("a"));
227 r.add(7u32);
228 r.set_owner(B);
229 r.add(Widget("b"));
230
231 r.purge_owner(A);
232 assert_eq!(r.items::<Widget>(), vec![&Widget("b")]);
234 assert!(r.items::<u32>().is_empty());
235 }
236
237 #[test]
238 fn distinct_types_are_independent() {
239 let mut r = Registry::new();
240 r.set_owner(A);
241 r.add(Widget("w"));
242 r.add(7u32);
243 assert_eq!(r.items::<Widget>(), vec![&Widget("w")]);
244 assert_eq!(r.items::<u32>(), vec![&7u32]);
245 }
246}