use std::any::{Any, TypeId};
use std::collections::HashMap;
use crate::module::ModuleId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContributeMode {
#[default]
Append,
Replace,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ContributeOpts {
pub priority: i32,
pub mode: ContributeMode,
}
struct Entry {
owner: ModuleId,
priority: i32,
mode: ContributeMode,
item: Box<dyn Any>,
}
pub struct Contribution<'a, T> {
pub owner: ModuleId,
pub priority: i32,
pub mode: ContributeMode,
pub item: &'a T,
}
#[derive(Default)]
pub struct Registry {
current: Option<ModuleId>,
by_type: HashMap<TypeId, Vec<Entry>>,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn set_owner(&mut self, owner: ModuleId) {
self.current = Some(owner);
}
pub fn add<T: 'static>(&mut self, item: T) {
self.add_with(item, ContributeOpts::default());
}
pub fn add_with<T: 'static>(&mut self, item: T, opts: ContributeOpts) {
let owner = self
.current
.expect("Registry::add called outside a module register() pass");
self.by_type
.entry(TypeId::of::<T>())
.or_default()
.push(Entry {
owner,
priority: opts.priority,
mode: opts.mode,
item: Box::new(item),
});
}
pub fn contributions<T: 'static>(&self) -> Vec<Contribution<'_, T>> {
let Some(entries) = self.by_type.get(&TypeId::of::<T>()) else {
return Vec::new();
};
let start = entries
.iter()
.rposition(|e| e.mode == ContributeMode::Replace)
.unwrap_or(0);
let mut resolved: Vec<&Entry> = entries[start..].iter().collect();
resolved.sort_by_key(|a| std::cmp::Reverse(a.priority));
resolved
.into_iter()
.map(|e| Contribution {
owner: e.owner,
priority: e.priority,
mode: e.mode,
item: e
.item
.downcast_ref::<T>()
.expect("keyed by TypeId::of::<T>"),
})
.collect()
}
pub fn items<T: 'static>(&self) -> Vec<&T> {
self.contributions::<T>()
.into_iter()
.map(|c| c.item)
.collect()
}
pub fn take<T: 'static>(&mut self) -> Vec<T> {
let Some(entries) = self.by_type.remove(&TypeId::of::<T>()) else {
return Vec::new();
};
let start = entries
.iter()
.rposition(|e| e.mode == ContributeMode::Replace)
.unwrap_or(0);
let mut kept: Vec<Entry> = entries.into_iter().skip(start).collect();
kept.sort_by_key(|a| std::cmp::Reverse(a.priority));
kept.into_iter()
.map(|e| *e.item.downcast::<T>().expect("keyed by TypeId::of::<T>"))
.collect()
}
pub fn purge_owner(&mut self, owner: ModuleId) {
for entries in self.by_type.values_mut() {
entries.retain(|e| e.owner != owner);
}
self.by_type.retain(|_, entries| !entries.is_empty());
}
}
#[cfg(test)]
mod tests {
use super::*;
const A: ModuleId = ModuleId::new("acme.a");
const B: ModuleId = ModuleId::new("acme.b");
#[derive(Debug, PartialEq)]
struct Widget(&'static str);
#[test]
fn collects_in_order_and_stamps_owner() {
let mut r = Registry::new();
r.set_owner(A);
r.add(Widget("one"));
r.set_owner(B);
r.add(Widget("two"));
let cs = r.contributions::<Widget>();
assert_eq!(cs.len(), 2);
assert_eq!(cs[0].item, &Widget("one"));
assert_eq!(cs[0].owner, A);
assert_eq!(cs[1].owner, B);
assert_eq!(r.items::<Widget>(), vec![&Widget("one"), &Widget("two")]);
}
#[test]
fn higher_priority_sorts_first_ties_keep_order() {
let mut r = Registry::new();
r.set_owner(A);
r.add(Widget("low"));
r.add_with(
Widget("high"),
ContributeOpts {
priority: 10,
mode: ContributeMode::Append,
},
);
r.add(Widget("low2"));
assert_eq!(
r.items::<Widget>(),
vec![&Widget("high"), &Widget("low"), &Widget("low2")]
);
}
#[test]
fn replace_supersedes_earlier() {
let mut r = Registry::new();
r.set_owner(A);
r.add(Widget("default"));
r.set_owner(B);
r.add_with(
Widget("override"),
ContributeOpts {
priority: 0,
mode: ContributeMode::Replace,
},
);
assert_eq!(r.items::<Widget>(), vec![&Widget("override")]);
}
#[test]
fn purge_owner_drops_only_that_owners_contributions() {
let mut r = Registry::new();
r.set_owner(A);
r.add(Widget("a"));
r.add(7u32);
r.set_owner(B);
r.add(Widget("b"));
r.purge_owner(A);
assert_eq!(r.items::<Widget>(), vec![&Widget("b")]);
assert!(r.items::<u32>().is_empty());
}
#[test]
fn distinct_types_are_independent() {
let mut r = Registry::new();
r.set_owner(A);
r.add(Widget("w"));
r.add(7u32);
assert_eq!(r.items::<Widget>(), vec![&Widget("w")]);
assert_eq!(r.items::<u32>(), vec![&7u32]);
}
}