Skip to main content

steel_registry/
macros.rs

1/// Implements `RegistryExt` for a registry type.
2///
3/// Expects `$id_field` to be `Vec<&'static $Entry>`.
4#[macro_export]
5macro_rules! impl_registry_ext {
6    ($Registry:ty, $Entry:ty, $id_field:ident, $key_field:ident) => {
7        impl $crate::RegistryExt for $Registry {
8            type Entry = $Entry;
9
10            fn freeze(&mut self) {
11                self.allows_registering = false;
12            }
13
14            fn by_id(&self, id: usize) -> Option<&'static $Entry> {
15                self.$id_field.get(id).copied()
16            }
17
18            fn by_key(&self, key: &steel_utils::Identifier) -> Option<&'static $Entry> {
19                self.$key_field
20                    .get(key)
21                    .and_then(|&id| self.$id_field.get(id).copied())
22            }
23
24            fn id_from_key(&self, key: &steel_utils::Identifier) -> Option<usize> {
25                self.$key_field.get(key).copied()
26            }
27
28            fn len(&self) -> usize {
29                self.$id_field.len()
30            }
31
32            fn is_empty(&self) -> bool {
33                self.$id_field.is_empty()
34            }
35        }
36    };
37}
38
39/// Implements registry-identity equality for an entry type by key.
40#[macro_export]
41macro_rules! impl_registry_entry_eq {
42    ($Entry:ty) => {
43        impl ::core::cmp::PartialEq for $Entry {
44            fn eq(&self, other: &Self) -> bool {
45                self.key == other.key
46            }
47        }
48
49        impl ::core::cmp::Eq for $Entry {}
50    };
51}
52
53/// Implements `RegistryEntry` for an entry type via hash map lookup.
54#[macro_export]
55macro_rules! impl_registry_entry {
56    ($Entry:ty, $global_field:ident) => {
57        $crate::impl_registry_entry_eq!($Entry);
58
59        impl $crate::RegistryEntry for $Entry {
60            fn key(&self) -> &steel_utils::Identifier {
61                &self.key
62            }
63
64            fn try_id(&self) -> Option<usize> {
65                use $crate::RegistryExt;
66                $crate::REGISTRY.$global_field.id_from_key(&self.key)
67            }
68        }
69    };
70}
71
72/// Implements the default register and iter methods in the registries.
73///
74/// An optional final error format enables duplicate-key rejection for registries
75/// whose entries carry behavior that must remain tied to their registered identity.
76#[macro_export]
77macro_rules! impl_standard_methods {
78    (
79        $Registry:ty,
80        $Entry:ty,
81        $id_field:ident,
82        $key_field:ident,
83        $allow_registering:ident
84        $(, $duplicate_key_error:literal)?
85    ) => {
86        impl $Registry {
87            pub fn register(&mut self, entry: $Entry) -> usize {
88                assert!(
89                    self.$allow_registering,
90                    concat!(
91                        "Cannot register ",
92                        stringify!($Entry),
93                        " after registry has been frozen"
94                    )
95                );
96                $(
97                    assert!(
98                        !self.$key_field.contains_key(&entry.key),
99                        $duplicate_key_error,
100                        entry.key
101                    );
102                )?
103                let id = self.$id_field.len();
104                self.$id_field.push(entry);
105                self.$key_field.insert(entry.key.clone(), id);
106                id
107            }
108
109            pub fn iter(&self) -> impl Iterator<Item = (usize, $Entry)> + '_ {
110                self.$id_field
111                    .iter()
112                    .enumerate()
113                    .map(|(id, &entry)| (id, entry))
114            }
115        }
116
117        impl Default for $Registry {
118            fn default() -> Self {
119                Self::new()
120            }
121        }
122    };
123}
124
125/// Implements both `RegistryExt` and `RegistryEntry` for a standard registry.
126#[macro_export]
127macro_rules! impl_registry {
128    ($Registry:ty, $Entry:ty, $id_field:ident, $key_field:ident, $global_field:ident) => {
129        $crate::impl_registry_ext!($Registry, $Entry, $id_field, $key_field);
130        $crate::impl_registry_entry!($Entry, $global_field);
131    };
132}
133
134/// Implements `TaggedRegistryExt` for a registry with tag support.
135#[macro_export]
136macro_rules! impl_tagged_registry {
137    ($Registry:ty, $key_field:ident, $entity_name:literal) => {
138        impl $crate::TaggedRegistryExt for $Registry {
139            fn register_tag(&mut self, tag: steel_utils::Identifier, keys: &[&'static str]) {
140                assert!(
141                    self.allows_registering,
142                    "Cannot register tags after registry has been frozen"
143                );
144
145                let entry_keys = keys
146                    .iter()
147                    .filter_map(|key| {
148                        let ident = steel_utils::registry::registry_vanilla_or_custom_tag(key);
149                        $crate::RegistryExt::by_key(self, &ident).map($crate::RegistryEntry::key)
150                    })
151                    .collect();
152
153                self.tags.insert(tag, entry_keys);
154            }
155
156            fn modify_tag(
157                &mut self,
158                tag: &steel_utils::Identifier,
159                f: impl FnOnce(Vec<steel_utils::Identifier>) -> Vec<steel_utils::Identifier>,
160            ) {
161                let existing = self
162                    .tags
163                    .remove(tag)
164                    .unwrap_or_default()
165                    .into_iter()
166                    .cloned()
167                    .collect();
168                let entry_keys = f(existing)
169                    .into_iter()
170                    .filter_map(|key| {
171                        let Some(entry) = $crate::RegistryExt::by_key(self, &key) else {
172                            tracing::error!(
173                                "{} {} not found in registry, skipping from tag {}",
174                                $entity_name,
175                                key,
176                                tag,
177                            );
178                            return None;
179                        };
180                        Some($crate::RegistryEntry::key(entry))
181                    })
182                    .collect();
183                self.tags.insert(tag.clone(), entry_keys);
184            }
185
186            fn is_in_tag(&self, entry: &Self::Entry, tag: &steel_utils::Identifier) -> bool {
187                self.tags.contains(tag, $crate::RegistryEntry::key(entry))
188            }
189
190            fn get_tag(&self, tag: &steel_utils::Identifier) -> Option<Vec<&'static Self::Entry>> {
191                self.tags.get(tag).map(|entry_keys| {
192                    entry_keys
193                        .iter()
194                        .filter_map(|key| $crate::RegistryExt::by_key(self, key))
195                        .collect()
196                })
197            }
198
199            fn iter_tag(
200                &self,
201                tag: &steel_utils::Identifier,
202            ) -> impl Iterator<Item = &'static Self::Entry> + '_ {
203                self.tags.get(tag).into_iter().flat_map(|entry_keys| {
204                    entry_keys
205                        .iter()
206                        .filter_map(|key| $crate::RegistryExt::by_key(self, key))
207                })
208            }
209
210            fn tag_keys(&self) -> impl Iterator<Item = &steel_utils::Identifier> + '_ {
211                self.tags.keys()
212            }
213        }
214    };
215}