1use std::num::NonZeroU16;
2
3use anyhow::Error;
4use once_cell::sync::Lazy;
5use rustc_hash::FxHashMap;
6
7use crate::{
8 TraitType, ValueType,
9 id::{FunctionId, TraitTypeId, ValueTypeId},
10 macro_helpers::CollectableFunction,
11 native_function::NativeFunction,
12 value_type::{CollectableTrait, CollectableValueType},
13};
14
15trait RegistryItem: 'static + Eq + std::hash::Hash {
20 type Id: Copy + From<NonZeroU16> + std::ops::Deref<Target = u16> + std::fmt::Display;
22 const TYPE_NAME: &'static str;
23
24 fn global_name(&self) -> &'static str;
26}
27
28impl RegistryItem for NativeFunction {
29 type Id = FunctionId;
30 const TYPE_NAME: &'static str = "Function";
31
32 fn global_name(&self) -> &'static str {
33 self.global_name
34 }
35}
36
37impl RegistryItem for ValueType {
38 type Id = ValueTypeId;
39 const TYPE_NAME: &'static str = "Value";
40
41 fn global_name(&self) -> &'static str {
42 self.global_name
43 }
44}
45
46impl RegistryItem for TraitType {
47 type Id = TraitTypeId;
48 const TYPE_NAME: &'static str = "Trait";
49 fn global_name(&self) -> &'static str {
50 self.global_name
51 }
52}
53
54struct Registry<T: RegistryItem> {
58 id_to_item: Box<[&'static T]>,
59 item_to_id: FxHashMap<&'static T, T::Id>,
60}
61
62impl<T: RegistryItem> Registry<T> {
63 fn new_from_items(mut items: Vec<&'static T>) -> Self {
67 items.sort_unstable_by_key(|item| item.global_name());
69
70 let mut item_to_id = FxHashMap::with_capacity_and_hasher(items.len(), Default::default());
71
72 let mut id = NonZeroU16::MIN;
73 let mut prev_name: Option<&str> = None;
74 for &item in items.iter() {
75 let global_name = item.global_name();
76 if let Some(prev) = prev_name {
77 assert!(
78 prev != global_name,
79 "multiple {ty} items registered with name: {global_name}!",
80 ty = T::TYPE_NAME
81 );
82 }
83 prev_name = Some(global_name);
84 item_to_id.insert(item, id.into());
85 id = id.checked_add(1).expect("overflowing item ids");
86 }
87
88 Self {
89 id_to_item: items.into_boxed_slice(),
90 item_to_id,
91 }
92 }
93
94 fn get_item(&self, id: T::Id) -> &'static T {
96 self.id_to_item[*id as usize - 1]
97 }
98
99 fn get_id(&self, item: &'static T) -> T::Id {
101 match self.item_to_id.get(&item) {
102 Some(id) => *id,
103 None => panic!(
104 "{ty} isn't registered: {item}",
105 ty = T::TYPE_NAME,
106 item = item.global_name()
107 ),
108 }
109 }
110
111 fn validate_id(&self, id: T::Id) -> Option<Error> {
113 let len = self.id_to_item.len();
114 if *id as usize <= len {
115 None
116 } else {
117 Some(anyhow::anyhow!(
118 "Invalid {ty} id, {id} expected a value <= {len}",
119 ty = T::TYPE_NAME
120 ))
121 }
122 }
123}
124
125static FUNCTIONS: Lazy<Registry<NativeFunction>> = Lazy::new(|| {
126 let functions = inventory::iter::<CollectableFunction>
127 .into_iter()
128 .map(|c| &**c.0)
129 .collect::<Vec<_>>();
130 Registry::new_from_items(functions)
131});
132
133pub fn get_native_function(id: FunctionId) -> &'static NativeFunction {
134 FUNCTIONS.get_item(id)
135}
136
137pub fn get_function_id(func: &'static NativeFunction) -> FunctionId {
138 FUNCTIONS.get_id(func)
139}
140
141pub fn validate_function_id(id: FunctionId) -> Option<Error> {
142 FUNCTIONS.validate_id(id)
143}
144
145static VALUES: Lazy<Registry<ValueType>> = Lazy::new(|| {
146 let all_values = inventory::iter::<CollectableValueType>
149 .into_iter()
150 .map(|t| &**t.0)
151 .collect::<Vec<_>>();
152 Registry::new_from_items(all_values)
153});
154
155pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId {
156 VALUES.get_id(value)
157}
158
159pub fn get_value_type(id: ValueTypeId) -> &'static ValueType {
160 VALUES.get_item(id)
161}
162
163pub fn validate_value_type_id(id: ValueTypeId) -> Option<Error> {
164 VALUES.validate_id(id)
165}
166
167static TRAITS: Lazy<Registry<TraitType>> = Lazy::new(|| {
168 let all_traits = inventory::iter::<CollectableTrait>
171 .into_iter()
172 .map(|t| &**t.0)
173 .collect::<Vec<_>>();
174 Registry::new_from_items(all_traits)
175});
176
177pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId {
178 TRAITS.get_id(trait_type)
179}
180
181pub fn get_trait(id: TraitTypeId) -> &'static TraitType {
182 TRAITS.get_item(id)
183}
184
185pub fn validate_trait_type_id(id: TraitTypeId) -> Option<Error> {
186 TRAITS.validate_id(id)
187}