steel_registry/registry/
reference.rs1use std::fmt::Debug;
4use std::io::{Cursor, Error, Result, Write};
5use std::str::FromStr;
6
7use simdnbt::owned::NbtTag;
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::Identifier;
10use steel_utils::codec::VarInt;
11use steel_utils::hash::{ComponentHasher, HashComponent};
12use steel_utils::serial::{ReadFrom, WriteTo};
13
14use crate::cat_sound_variant::CatSoundVariant;
15use crate::cat_variant::CatVariant;
16use crate::chicken_sound_variant::ChickenSoundVariant;
17use crate::chicken_variant::ChickenVariant;
18use crate::cow_sound_variant::CowSoundVariant;
19use crate::cow_variant::CowVariant;
20use crate::frog_variant::FrogVariant;
21use crate::map_decoration_type::MapDecorationType;
22use crate::painting_variant::PaintingVariant;
23use crate::pig_sound_variant::PigSoundVariant;
24use crate::pig_variant::PigVariant;
25use crate::potion::Potion;
26use crate::villager_type::VillagerType;
27use crate::wolf_sound_variant::WolfSoundVariant;
28use crate::wolf_variant::WolfVariant;
29use crate::zombie_nautilus_variant::ZombieNautilusVariant;
30use crate::{REGISTRY, RegistryEntry, RegistryExt};
31
32pub trait RegistryReferenceEntry: RegistryEntry + Debug + Send + Sync {
34 const REGISTRY_NAME: &'static str;
36
37 fn reference_by_id(id: usize) -> Option<&'static Self>;
39
40 fn reference_by_key(key: &Identifier) -> Option<&'static Self>;
42}
43
44#[derive(Debug)]
47pub struct RegistryReference<T: RegistryReferenceEntry> {
48 value: &'static T,
49}
50
51impl<T: RegistryReferenceEntry> RegistryReference<T> {
52 #[must_use]
53 pub const fn new(value: &'static T) -> Self {
54 Self { value }
55 }
56
57 #[must_use]
58 pub const fn value(&self) -> &'static T {
59 self.value
60 }
61}
62
63impl<T: RegistryReferenceEntry> Clone for RegistryReference<T> {
64 fn clone(&self) -> Self {
65 *self
66 }
67}
68
69impl<T: RegistryReferenceEntry> Copy for RegistryReference<T> {}
70
71impl<T: RegistryReferenceEntry> PartialEq for RegistryReference<T> {
72 fn eq(&self, other: &Self) -> bool {
73 self.value.key() == other.value.key()
74 }
75}
76
77impl<T: RegistryReferenceEntry> Eq for RegistryReference<T> {}
78
79impl<T: RegistryReferenceEntry> WriteTo for RegistryReference<T> {
80 fn write(&self, writer: &mut impl Write) -> Result<()> {
81 let id = self.value.try_id().ok_or_else(|| {
82 Error::other(format!(
83 "Unknown {}: {}",
84 T::REGISTRY_NAME,
85 self.value.key()
86 ))
87 })?;
88 let id = i32::try_from(id).map_err(|_| {
89 Error::other(format!(
90 "{} id out of protocol range: {id}",
91 T::REGISTRY_NAME
92 ))
93 })?;
94 VarInt(id).write(writer)
95 }
96}
97
98impl<T: RegistryReferenceEntry> ReadFrom for RegistryReference<T> {
99 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
100 let encoded_id = VarInt::read(data)?.0;
101 let id = usize::try_from(encoded_id).map_err(|_| {
102 Error::other(format!(
103 "Negative {} registry id: {encoded_id}",
104 T::REGISTRY_NAME
105 ))
106 })?;
107 T::reference_by_id(id)
108 .map(Self::new)
109 .ok_or_else(|| Error::other(format!("Unknown {} registry id: {id}", T::REGISTRY_NAME)))
110 }
111}
112
113impl<T: RegistryReferenceEntry> ToNbtTag for RegistryReference<T> {
114 fn to_nbt_tag(self) -> NbtTag {
115 NbtTag::String(self.value.key().to_string().into())
116 }
117}
118
119impl<T: RegistryReferenceEntry> FromNbtTag for RegistryReference<T> {
120 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
121 let key = Identifier::from_str(&tag.string()?.to_str()).ok()?;
122 T::reference_by_key(&key).map(Self::new)
123 }
124}
125
126impl<T: RegistryReferenceEntry> HashComponent for RegistryReference<T> {
127 fn hash_component(&self, hasher: &mut ComponentHasher) {
128 self.value.key().to_string().hash_component(hasher);
129 }
130}
131
132macro_rules! impl_registry_reference_entry {
133 ($entry:ty, $registry:ident, $name:literal) => {
134 impl RegistryReferenceEntry for $entry {
135 const REGISTRY_NAME: &'static str = $name;
136
137 fn reference_by_id(id: usize) -> Option<&'static Self> {
138 REGISTRY.$registry.by_id(id)
139 }
140
141 fn reference_by_key(key: &Identifier) -> Option<&'static Self> {
142 REGISTRY.$registry.by_key(key)
143 }
144 }
145 };
146}
147
148impl_registry_reference_entry!(VillagerType, villager_types, "villager type");
149impl_registry_reference_entry!(WolfVariant, wolf_variants, "wolf variant");
150impl_registry_reference_entry!(WolfSoundVariant, wolf_sound_variants, "wolf sound variant");
151impl_registry_reference_entry!(PigVariant, pig_variants, "pig variant");
152impl_registry_reference_entry!(PigSoundVariant, pig_sound_variants, "pig sound variant");
153impl_registry_reference_entry!(CowVariant, cow_variants, "cow variant");
154impl_registry_reference_entry!(CowSoundVariant, cow_sound_variants, "cow sound variant");
155impl_registry_reference_entry!(ChickenVariant, chicken_variants, "chicken variant");
156impl_registry_reference_entry!(
157 ChickenSoundVariant,
158 chicken_sound_variants,
159 "chicken sound variant"
160);
161impl_registry_reference_entry!(
162 ZombieNautilusVariant,
163 zombie_nautilus_variants,
164 "zombie nautilus variant"
165);
166impl_registry_reference_entry!(FrogVariant, frog_variants, "frog variant");
167impl_registry_reference_entry!(CatVariant, cat_variants, "cat variant");
168impl_registry_reference_entry!(CatSoundVariant, cat_sound_variants, "cat sound variant");
169impl_registry_reference_entry!(PaintingVariant, painting_variants, "painting variant");
170impl_registry_reference_entry!(Potion, potions, "potion");
171impl_registry_reference_entry!(
172 MapDecorationType,
173 map_decoration_types,
174 "map decoration type"
175);
176
177#[cfg(test)]
178mod tests {
179 use std::io::Cursor;
180
181 use simdnbt::borrow::read_tag;
182 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
183 use steel_utils::codec::VarInt;
184 use steel_utils::hash::HashComponent as _;
185 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
186
187 use super::{RegistryReference, RegistryReferenceEntry};
188 use crate::cat_sound_variant::CatSoundVariant;
189 use crate::cat_variant::CatVariant;
190 use crate::chicken_sound_variant::ChickenSoundVariant;
191 use crate::chicken_variant::ChickenVariant;
192 use crate::cow_sound_variant::CowSoundVariant;
193 use crate::cow_variant::CowVariant;
194 use crate::frog_variant::FrogVariant;
195 use crate::init_vanilla_registry;
196 use crate::map_decoration_type::MapDecorationType;
197 use crate::painting_variant::PaintingVariant;
198 use crate::pig_sound_variant::PigSoundVariant;
199 use crate::pig_variant::PigVariant;
200 use crate::villager_type::VillagerType;
201 use crate::wolf_sound_variant::WolfSoundVariant;
202 use crate::wolf_variant::WolfVariant;
203 use crate::zombie_nautilus_variant::ZombieNautilusVariant;
204
205 fn assert_codecs<T: RegistryReferenceEntry>() {
206 let entry = T::reference_by_id(0).expect("vanilla registry should not be empty");
207 let reference = RegistryReference::new(entry);
208
209 let mut network = Vec::new();
210 reference
211 .write(&mut network)
212 .expect("registry reference should encode");
213 assert_eq!(
214 RegistryReference::<T>::read(&mut Cursor::new(network.as_slice()))
215 .expect("registry reference should decode"),
216 reference
217 );
218
219 let nbt = reference.to_nbt_tag();
220 assert_eq!(reference.compute_hash(), nbt.compute_hash());
221 let mut bytes = Vec::new();
222 nbt.write(&mut bytes);
223 let borrowed = read_tag(&mut Cursor::new(bytes.as_slice()))
224 .expect("registry reference NBT should parse");
225 assert_eq!(
226 RegistryReference::<T>::from_nbt_tag(borrowed.as_tag()),
227 Some(reference)
228 );
229 }
230
231 #[test]
232 fn fixed_variant_references_share_vanilla_holder_codecs() {
233 init_vanilla_registry();
234 assert_codecs::<VillagerType>();
235 assert_codecs::<WolfVariant>();
236 assert_codecs::<WolfSoundVariant>();
237 assert_codecs::<PigVariant>();
238 assert_codecs::<PigSoundVariant>();
239 assert_codecs::<CowVariant>();
240 assert_codecs::<CowSoundVariant>();
241 assert_codecs::<ChickenVariant>();
242 assert_codecs::<ChickenSoundVariant>();
243 assert_codecs::<ZombieNautilusVariant>();
244 assert_codecs::<FrogVariant>();
245 assert_codecs::<CatVariant>();
246 assert_codecs::<CatSoundVariant>();
247 assert_codecs::<PaintingVariant>();
248 assert_codecs::<MapDecorationType>();
249 }
250
251 #[test]
252 fn fixed_variant_references_reject_unknown_network_ids() {
253 init_vanilla_registry();
254 let mut network = Vec::new();
255 VarInt(i32::MAX)
256 .write(&mut network)
257 .expect("invalid test id should encode");
258 assert!(
259 RegistryReference::<VillagerType>::read(&mut Cursor::new(network.as_slice())).is_err()
260 );
261 }
262}