Skip to main content

steel_registry/registry/
reference.rs

1//! Registry references used by fixed holder codecs.
2
3use 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
32/// Registry operations required by [`RegistryReference`].
33pub trait RegistryReferenceEntry: RegistryEntry + Debug + Send + Sync {
34    /// Human-readable registry name used in codec errors.
35    const REGISTRY_NAME: &'static str;
36
37    /// Looks up an entry by its protocol registry ID.
38    fn reference_by_id(id: usize) -> Option<&'static Self>;
39
40    /// Looks up an entry by its registry key.
41    fn reference_by_key(key: &Identifier) -> Option<&'static Self>;
42}
43
44/// A registry-owned value encoded by `RegistryFixedCodec` and
45/// `ByteBufCodecs.holderRegistry`.
46#[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}