Skip to main content

steel_registry/
banner_pattern.rs

1//! Banner pattern registry values.
2
3use std::borrow::Cow;
4use std::io::{Cursor, Error, Result, Write};
5
6use rustc_hash::FxHashMap;
7use simdnbt::owned::{NbtCompound, NbtTag};
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::Identifier;
10use steel_utils::codec::VarInt;
11use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
12use steel_utils::serial::{PrefixedRead, PrefixedWrite, ReadFrom, WriteTo};
13
14use crate::{REGISTRY, RegistryExt, RegistryHolderEntry, RegistryTags};
15
16const MAX_NETWORK_STRING_LENGTH: usize = 32_767;
17const MAX_NETWORK_STRING_BYTES: usize = MAX_NETWORK_STRING_LENGTH * 3;
18
19/// Complete registry-independent banner pattern definition.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct BannerPatternValue {
22    asset_id: Identifier,
23    translation_key: Cow<'static, str>,
24}
25
26impl BannerPatternValue {
27    #[must_use]
28    pub const fn new(asset_id: Identifier, translation_key: Cow<'static, str>) -> Self {
29        Self {
30            asset_id,
31            translation_key,
32        }
33    }
34
35    #[must_use]
36    pub const fn asset_id(&self) -> &Identifier {
37        &self.asset_id
38    }
39
40    #[must_use]
41    pub fn translation_key(&self) -> &str {
42        &self.translation_key
43    }
44
45    fn to_nbt_tag_ref(&self) -> NbtTag {
46        let mut compound = NbtCompound::new();
47        compound.insert("asset_id", self.asset_id.clone());
48        compound.insert("translation_key", self.translation_key.as_ref());
49        NbtTag::Compound(compound)
50    }
51}
52
53impl WriteTo for BannerPatternValue {
54    fn write(&self, writer: &mut impl Write) -> Result<()> {
55        self.asset_id.write(writer)?;
56        write_network_string(&self.translation_key, writer)
57    }
58}
59
60impl ReadFrom for BannerPatternValue {
61    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
62        Ok(Self::new(
63            Identifier::read(data)?,
64            Cow::Owned(read_network_string(data)?),
65        ))
66    }
67}
68
69impl ToNbtTag for BannerPatternValue {
70    fn to_nbt_tag(self) -> NbtTag {
71        self.to_nbt_tag_ref()
72    }
73}
74
75impl FromNbtTag for BannerPatternValue {
76    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
77        let compound = tag.compound()?;
78        let asset_id = Identifier::from_nbt_tag(compound.get("asset_id")?)?;
79        let translation_key = compound.get("translation_key")?.string()?.to_str();
80        Some(Self::new(
81            asset_id,
82            Cow::Owned(translation_key.into_owned()),
83        ))
84    }
85}
86
87impl HashComponent for BannerPatternValue {
88    fn hash_component(&self, hasher: &mut ComponentHasher) {
89        let mut entries = Vec::with_capacity(2);
90        push_hash_entry(&mut entries, "asset_id", &self.asset_id);
91        push_hash_entry(
92            &mut entries,
93            "translation_key",
94            self.translation_key.as_ref(),
95        );
96        sort_map_entries(&mut entries);
97        hasher.start_map();
98        for entry in &entries {
99            hasher.put_raw_bytes(&entry.key_bytes);
100            hasher.put_raw_bytes(&entry.value_bytes);
101        }
102        hasher.end_map();
103    }
104}
105
106fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
107    let mut key_hasher = ComponentHasher::new();
108    key.hash_component(&mut key_hasher);
109    let mut value_hasher = ComponentHasher::new();
110    value.hash_component(&mut value_hasher);
111    entries.push(HashEntry::new(key_hasher, value_hasher));
112}
113
114fn write_network_string(value: &str, writer: &mut impl Write) -> Result<()> {
115    if value.encode_utf16().count() > MAX_NETWORK_STRING_LENGTH {
116        return Err(Error::other("String is longer than 32767 UTF-16 units"));
117    }
118    if value.len() > MAX_NETWORK_STRING_BYTES {
119        return Err(Error::other("Encoded string is longer than 98301 bytes"));
120    }
121    value.write_prefixed::<VarInt>(writer)
122}
123
124fn read_network_string(data: &mut Cursor<&[u8]>) -> Result<String> {
125    let value = String::read_prefixed_bound::<VarInt>(data, MAX_NETWORK_STRING_BYTES)?;
126    if value.encode_utf16().count() > MAX_NETWORK_STRING_LENGTH {
127        return Err(Error::other("String is longer than 32767 UTF-16 units"));
128    }
129    Ok(value)
130}
131
132/// Registered banner pattern definition.
133#[derive(Debug)]
134pub struct BannerPattern {
135    pub key: Identifier,
136    value: BannerPatternValue,
137}
138
139impl BannerPattern {
140    #[must_use]
141    pub const fn new(key: Identifier, value: BannerPatternValue) -> Self {
142        Self { key, value }
143    }
144
145    #[must_use]
146    pub const fn value(&self) -> &BannerPatternValue {
147        &self.value
148    }
149}
150
151impl ToNbtTag for &BannerPattern {
152    fn to_nbt_tag(self) -> NbtTag {
153        self.value.to_nbt_tag_ref()
154    }
155}
156
157pub type BannerPatternRef = &'static BannerPattern;
158
159pub struct BannerPatternRegistry {
160    banner_patterns_by_id: Vec<BannerPatternRef>,
161    banner_patterns_by_key: FxHashMap<Identifier, usize>,
162    tags: RegistryTags,
163    allows_registering: bool,
164}
165
166impl BannerPatternRegistry {
167    #[must_use]
168    pub fn new() -> Self {
169        Self {
170            banner_patterns_by_id: Vec::new(),
171            banner_patterns_by_key: FxHashMap::default(),
172            tags: RegistryTags::default(),
173            allows_registering: true,
174        }
175    }
176}
177
178crate::impl_standard_methods!(
179    BannerPatternRegistry,
180    BannerPatternRef,
181    banner_patterns_by_id,
182    banner_patterns_by_key,
183    allows_registering
184);
185
186crate::impl_registry!(
187    BannerPatternRegistry,
188    BannerPattern,
189    banner_patterns_by_id,
190    banner_patterns_by_key,
191    banner_patterns
192);
193
194crate::impl_tagged_registry!(
195    BannerPatternRegistry,
196    banner_patterns_by_key,
197    "banner pattern"
198);
199
200impl RegistryHolderEntry for BannerPattern {
201    type Value = BannerPatternValue;
202
203    const REGISTRY_NAME: &'static str = "banner pattern";
204
205    fn holder_value(&self) -> &Self::Value {
206        &self.value
207    }
208
209    fn holder_by_id(id: usize) -> Option<&'static Self> {
210        REGISTRY.banner_patterns.by_id(id)
211    }
212
213    fn holder_by_key(key: &Identifier) -> Option<&'static Self> {
214        REGISTRY.banner_patterns.by_key(key)
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use std::borrow::Cow;
221    use std::io::Cursor;
222
223    use simdnbt::borrow::read_tag;
224    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
225    use steel_utils::Identifier;
226    use steel_utils::hash::HashComponent as _;
227    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
228
229    use super::BannerPatternValue;
230    use crate::init_vanilla_registry;
231    use crate::{REGISTRY, vanilla_banner_patterns};
232
233    fn parse(tag: simdnbt::owned::NbtTag) -> Option<BannerPatternValue> {
234        let mut bytes = Vec::new();
235        tag.write(&mut bytes);
236        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
237        BannerPatternValue::from_nbt_tag(borrowed.as_tag())
238    }
239
240    #[test]
241    fn generated_patterns_follow_vanilla_registry_order() {
242        init_vanilla_registry();
243        let keys = REGISTRY
244            .banner_patterns
245            .iter()
246            .map(|(_, pattern)| pattern.key.path.as_ref())
247            .collect::<Vec<_>>();
248        assert_eq!(
249            keys,
250            [
251                "base",
252                "square_bottom_left",
253                "square_bottom_right",
254                "square_top_left",
255                "square_top_right",
256                "stripe_bottom",
257                "stripe_top",
258                "stripe_left",
259                "stripe_right",
260                "stripe_center",
261                "stripe_middle",
262                "stripe_downright",
263                "stripe_downleft",
264                "small_stripes",
265                "cross",
266                "straight_cross",
267                "triangle_bottom",
268                "triangle_top",
269                "triangles_bottom",
270                "triangles_top",
271                "diagonal_left",
272                "diagonal_up_right",
273                "diagonal_up_left",
274                "diagonal_right",
275                "circle",
276                "rhombus",
277                "half_vertical",
278                "half_horizontal",
279                "half_vertical_right",
280                "half_horizontal_bottom",
281                "border",
282                "gradient",
283                "gradient_up",
284                "bricks",
285                "curly_border",
286                "globe",
287                "creeper",
288                "skull",
289                "flower",
290                "mojang",
291                "piglin",
292                "flow",
293                "guster",
294            ]
295        );
296    }
297
298    #[test]
299    fn direct_codecs_and_hash_match_vanilla_shape() {
300        init_vanilla_registry();
301        let pattern = vanilla_banner_patterns::BASE.value().clone();
302        let mut network = Vec::new();
303        pattern.write(&mut network).expect("pattern should encode");
304        assert_eq!(
305            BannerPatternValue::read(&mut Cursor::new(network.as_slice()))
306                .expect("pattern should decode"),
307            pattern
308        );
309
310        let nbt = pattern.clone().to_nbt_tag();
311        assert_eq!(parse(nbt.clone()), Some(pattern.clone()));
312        assert_eq!(pattern.compute_hash(), nbt.compute_hash());
313    }
314
315    #[test]
316    fn direct_codecs_reject_invalid_identifiers_and_long_network_strings() {
317        let mut compound = simdnbt::owned::NbtCompound::new();
318        compound.insert("asset_id", "Invalid Asset");
319        compound.insert("translation_key", "block.minecraft.banner.invalid");
320        assert!(parse(simdnbt::owned::NbtTag::Compound(compound)).is_none());
321
322        let too_long = BannerPatternValue::new(
323            Identifier::vanilla_static("base"),
324            Cow::Owned("x".repeat(32_768)),
325        );
326        assert!(too_long.write(&mut Vec::new()).is_err());
327    }
328}