@@ -12,168 +12,175 @@ use crate::{
12
12
value_type:: { CollectableTrait , CollectableValueType } ,
13
13
} ;
14
14
15
- struct Functions {
16
- id_to_value : Box < [ & ' static NativeFunction ] > ,
17
- value_to_id : FxHashMap < & ' static NativeFunction , FunctionId > ,
15
+ /// A trait for types that can be registered in a registry.
16
+ ///
17
+ /// This allows the generic registry to work with different types
18
+ /// while maintaining their specific requirements.
19
+ trait RegistryItem : ' static + Eq + std:: hash:: Hash {
20
+ /// The ID type used for this registry item
21
+ type Id : Copy + From < NonZeroU16 > + std:: ops:: Deref < Target = u16 > + std:: fmt:: Display ;
22
+ const TYPE_NAME : & ' static str ;
23
+
24
+ /// Get the global name used for sorting and uniqueness validation
25
+ fn global_name ( & self ) -> & ' static str ;
18
26
}
19
- static FUNCTIONS : Lazy < Functions > = Lazy :: new ( || {
20
- let mut functions = inventory:: iter :: < CollectableFunction >
21
- . into_iter ( )
22
- . map ( |c| & * * c. 0 )
23
- . collect :: < Vec < _ > > ( ) ;
24
- functions. sort_unstable_by_key ( |f| f. global_name ) ;
25
- let mut value_to_id = FxHashMap :: with_capacity_and_hasher ( functions. len ( ) , Default :: default ( ) ) ;
26
- let mut names = FxHashSet :: with_capacity_and_hasher ( functions. len ( ) , Default :: default ( ) ) ;
27
-
28
- let mut id = NonZeroU16 :: MIN ;
29
- for & native_function in functions. iter ( ) {
30
- value_to_id. insert ( native_function, id. into ( ) ) ;
31
- let global_name = native_function. global_name ;
32
- assert ! (
33
- names. insert( global_name) ,
34
- "multiple functions registered with name: {global_name}!"
35
- ) ;
36
- id = id. checked_add ( 1 ) . expect ( "overflowing function ids" ) ;
27
+
28
+ impl 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
+
37
+ impl 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
+
46
+ impl 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
+
54
+ /// A generic registry that maps between IDs and static references to items.
55
+ ///
56
+ /// This eliminates the code duplication between Functions, Values, and Traits registries.
57
+ struct Registry < T : RegistryItem > {
58
+ id_to_item : Box < [ & ' static T ] > ,
59
+ item_to_id : FxHashMap < & ' static T , T :: Id > ,
60
+ }
61
+
62
+ impl < T : RegistryItem > Registry < T > {
63
+ /// Create a new registry from a collection of items.
64
+ ///
65
+ /// Items are sorted by global_name to ensure stable ID assignment.
66
+ fn new_from_items ( mut items : Vec < & ' static T > ) -> Self {
67
+ // Sort by global name to get stable order
68
+ 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
+ let mut names = FxHashSet :: with_capacity_and_hasher ( items. len ( ) , Default :: default ( ) ) ;
72
+
73
+ let mut id = NonZeroU16 :: MIN ;
74
+ for & item in items. iter ( ) {
75
+ item_to_id. insert ( item, id. into ( ) ) ;
76
+ let global_name = item. global_name ( ) ;
77
+ assert ! (
78
+ names. insert( global_name) ,
79
+ "multiple {ty} items registered with name: {global_name}!" ,
80
+ ty = T :: TYPE_NAME
81
+ ) ;
82
+ id = id. checked_add ( 1 ) . expect ( "overflowing item ids" ) ;
83
+ }
84
+
85
+ Self {
86
+ id_to_item : items. into_boxed_slice ( ) ,
87
+ item_to_id,
88
+ }
89
+ }
90
+
91
+ /// Get an item by its ID
92
+ fn get_item ( & self , id : T :: Id ) -> & ' static T {
93
+ self . id_to_item [ * id as usize - 1 ]
94
+ }
95
+
96
+ /// Get the ID for an item
97
+ fn get_id ( & self , item : & ' static T ) -> T :: Id {
98
+ match self . item_to_id . get ( & item) {
99
+ Some ( id) => * id,
100
+ None => panic ! (
101
+ "{ty} isn't registered: {item}" ,
102
+ ty = T :: TYPE_NAME ,
103
+ item = item. global_name( )
104
+ ) ,
105
+ }
37
106
}
38
107
39
- Functions {
40
- id_to_value : functions. into_boxed_slice ( ) ,
41
- value_to_id,
108
+ /// Validate that an ID is within the valid range
109
+ fn validate_id ( & self , id : T :: Id ) -> Option < Error > {
110
+ let len = self . id_to_item . len ( ) ;
111
+ if * id as usize <= len {
112
+ None
113
+ } else {
114
+ Some ( anyhow:: anyhow!(
115
+ "Invalid {ty} id, {id} expected a value <= {len}" ,
116
+ ty = T :: TYPE_NAME
117
+ ) )
118
+ }
42
119
}
120
+ }
121
+
122
+ static FUNCTIONS : Lazy < Registry < NativeFunction > > = Lazy :: new ( || {
123
+ let functions = inventory:: iter :: < CollectableFunction >
124
+ . into_iter ( )
125
+ . map ( |c| & * * c. 0 )
126
+ . collect :: < Vec < _ > > ( ) ;
127
+ Registry :: new_from_items ( functions)
43
128
} ) ;
44
129
45
130
pub fn get_native_function ( id : FunctionId ) -> & ' static NativeFunction {
46
- FUNCTIONS . id_to_value [ * id as usize - 1 ]
131
+ FUNCTIONS . get_item ( id )
47
132
}
48
133
49
134
pub fn get_function_id ( func : & ' static NativeFunction ) -> FunctionId {
50
- * FUNCTIONS
51
- . value_to_id
52
- . get ( & func)
53
- . expect ( "function isn't registered" )
135
+ FUNCTIONS . get_id ( func)
54
136
}
55
137
56
138
pub fn validate_function_id ( id : FunctionId ) -> Option < Error > {
57
- let len = FUNCTIONS . id_to_value . len ( ) ;
58
- if * id as usize <= len {
59
- None
60
- } else {
61
- Some ( anyhow:: anyhow!(
62
- "Invalid function type id, {id} expected a value <= {len}"
63
- ) )
64
- }
65
- }
66
-
67
- struct Values {
68
- id_to_value : Box < [ & ' static ValueType ] > ,
69
- value_to_id : FxHashMap < & ' static ValueType , ValueTypeId > ,
139
+ FUNCTIONS . validate_id ( id)
70
140
}
71
141
72
- static VALUES : Lazy < Values > = Lazy :: new ( || {
142
+ static VALUES : Lazy < Registry < ValueType > > = Lazy :: new ( || {
73
143
// Inventory does not guarantee an order. So we sort by the global name to get a stable order
74
144
// This ensures that assigned ids are also stable.
75
145
// We don't currently take advantage of this but we could in the future. The remaining issue is
76
146
// ensuring the set of values is the same across runs.
77
- let mut all_values = inventory:: iter :: < CollectableValueType >
147
+ let all_values = inventory:: iter :: < CollectableValueType >
78
148
. into_iter ( )
79
149
. map ( |t| & * * t. 0 )
80
150
. collect :: < Vec < _ > > ( ) ;
81
- all_values. sort_unstable_by_key ( |t| t. global_name ) ;
82
-
83
- let mut value_to_id = FxHashMap :: with_capacity_and_hasher ( all_values. len ( ) , Default :: default ( ) ) ;
84
- // Our sort above is non-sensical if names are not unique
85
- let mut names = FxHashSet :: with_capacity_and_hasher ( all_values. len ( ) , Default :: default ( ) ) ;
86
-
87
- let mut id = NonZeroU16 :: MIN ;
88
- for & value_type in all_values. iter ( ) {
89
- value_to_id. insert ( value_type, id. into ( ) ) ;
90
- let global_name = value_type. global_name ;
91
- assert ! (
92
- names. insert( global_name) ,
93
- "two values registered with the same name: {global_name}"
94
- ) ;
95
- id = id. checked_add ( 1 ) . expect ( "overflowing value type ids" ) ;
96
- }
97
-
98
- Values {
99
- value_to_id,
100
- id_to_value : all_values. into_boxed_slice ( ) ,
101
- }
151
+ Registry :: new_from_items ( all_values)
102
152
} ) ;
103
153
104
154
pub fn get_value_type_id ( value : & ' static ValueType ) -> ValueTypeId {
105
- match VALUES . value_to_id . get ( value) {
106
- Some ( id) => * id,
107
- None => panic ! ( "Use of unregistered trait {value:?}" ) ,
108
- }
155
+ VALUES . get_id ( value)
109
156
}
110
157
111
158
pub fn get_value_type ( id : ValueTypeId ) -> & ' static ValueType {
112
- VALUES . id_to_value [ * id as usize - 1 ]
159
+ VALUES . get_item ( id )
113
160
}
114
161
115
162
pub fn validate_value_type_id ( id : ValueTypeId ) -> Option < Error > {
116
- let len = VALUES . id_to_value . len ( ) ;
117
- if * id as usize <= len {
118
- None
119
- } else {
120
- Some ( anyhow:: anyhow!(
121
- "Invalid value type id, {id} expected a value <= {len}"
122
- ) )
123
- }
124
- }
125
-
126
- struct Traits {
127
- id_to_trait : Box < [ & ' static TraitType ] > ,
128
- trait_to_id : FxHashMap < & ' static TraitType , TraitTypeId > ,
163
+ VALUES . validate_id ( id)
129
164
}
130
165
131
- static TRAITS : Lazy < Traits > = Lazy :: new ( || {
166
+ static TRAITS : Lazy < Registry < TraitType > > = Lazy :: new ( || {
132
167
// Inventory does not guarantee an order. So we sort by the global name to get a stable order
133
168
// This ensures that assigned ids are also stable.
134
- let mut all_traits = inventory:: iter :: < CollectableTrait >
169
+ let all_traits = inventory:: iter :: < CollectableTrait >
135
170
. into_iter ( )
136
171
. map ( |t| & * * t. 0 )
137
172
. collect :: < Vec < _ > > ( ) ;
138
- all_traits. sort_unstable_by_key ( |t| t. global_name ) ;
139
-
140
- let mut trait_to_id = FxHashMap :: with_capacity_and_hasher ( all_traits. len ( ) , Default :: default ( ) ) ;
141
- // Our sort above is non-sensical if names are not unique
142
- let mut names = FxHashSet :: with_capacity_and_hasher ( all_traits. len ( ) , Default :: default ( ) ) ;
143
- let mut id = NonZeroU16 :: MIN ;
144
- for & trait_type in all_traits. iter ( ) {
145
- trait_to_id. insert ( trait_type, id. into ( ) ) ;
146
-
147
- let global_name = trait_type. global_name ;
148
- assert ! (
149
- names. insert( global_name) ,
150
- "two traits registered with the same name: {global_name}"
151
- ) ;
152
- id = id. checked_add ( 1 ) . expect ( "overflowing trait type ids" ) ;
153
- }
154
- Traits {
155
- trait_to_id,
156
- id_to_trait : all_traits. into_boxed_slice ( ) ,
157
- }
173
+ Registry :: new_from_items ( all_traits)
158
174
} ) ;
159
175
160
176
pub fn get_trait_type_id ( trait_type : & ' static TraitType ) -> TraitTypeId {
161
- match TRAITS . trait_to_id . get ( trait_type) {
162
- Some ( id) => * id,
163
- None => panic ! ( "Use of unregistered trait {trait_type:?}" ) ,
164
- }
177
+ TRAITS . get_id ( trait_type)
165
178
}
166
179
167
180
pub fn get_trait ( id : TraitTypeId ) -> & ' static TraitType {
168
- TRAITS . id_to_trait [ * id as usize - 1 ]
181
+ TRAITS . get_item ( id )
169
182
}
183
+
170
184
pub fn validate_trait_type_id ( id : TraitTypeId ) -> Option < Error > {
171
- let len = TRAITS . id_to_trait . len ( ) ;
172
- if * id as usize <= len {
173
- None
174
- } else {
175
- Some ( anyhow:: anyhow!(
176
- "Invalid trait type id, {id} expected a value <= {len}"
177
- ) )
178
- }
185
+ TRAITS . validate_id ( id)
179
186
}
0 commit comments