steel_registry/
trim_material.rs1use std::io::{Cursor, Result as IoResult, Write};
4
5use rustc_hash::FxHashMap;
6use simdnbt::owned::{NbtCompound, NbtTag};
7use simdnbt::{FromNbtTag, ToNbtTag};
8use steel_utils::Identifier;
9use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
10use steel_utils::serial::{ReadFrom, WriteTo};
11use text_components::TextComponent;
12
13use crate::{REGISTRY, RegistryExt, RegistryHolderEntry, RegistryTags};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct TrimMaterialValue {
22 palette_id: Identifier,
23 description: TextComponent,
24}
25
26impl TrimMaterialValue {
27 #[must_use]
28 pub const fn new(palette_id: Identifier, description: TextComponent) -> Self {
29 Self {
30 palette_id,
31 description,
32 }
33 }
34
35 #[must_use]
36 pub const fn palette_id(&self) -> &Identifier {
37 &self.palette_id
38 }
39
40 #[must_use]
41 pub const fn description(&self) -> &TextComponent {
42 &self.description
43 }
44
45 fn to_nbt_tag_ref(&self) -> NbtTag {
46 let mut compound = NbtCompound::new();
47 compound.insert("palette_id", self.palette_id.clone().to_nbt_tag());
48 compound.insert("description", self.description.to_codec_nbt());
49 NbtTag::Compound(compound)
50 }
51}
52
53impl WriteTo for TrimMaterialValue {
54 fn write(&self, writer: &mut impl Write) -> IoResult<()> {
55 self.palette_id.write(writer)?;
56 WriteTo::write(&self.description.to_codec_nbt(), writer)
57 }
58}
59
60impl ReadFrom for TrimMaterialValue {
61 fn read(data: &mut Cursor<&[u8]>) -> IoResult<Self> {
62 Ok(Self::new(
63 Identifier::read(data)?,
64 TextComponent::read(data)?,
65 ))
66 }
67}
68
69impl ToNbtTag for TrimMaterialValue {
70 fn to_nbt_tag(self) -> NbtTag {
71 self.to_nbt_tag_ref()
72 }
73}
74
75impl FromNbtTag for TrimMaterialValue {
76 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
77 let compound = tag.compound()?;
78 let palette_id = Identifier::from_nbt_tag(compound.get("palette_id")?)?;
79 let description = TextComponent::from_nbt(&compound.get("description")?.to_owned())?;
80 Some(Self::new(palette_id, description))
81 }
82}
83
84impl HashComponent for TrimMaterialValue {
85 fn hash_component(&self, hasher: &mut ComponentHasher) {
86 let mut entries = Vec::with_capacity(2);
87 push_hash_entry(&mut entries, "palette_id", &self.palette_id);
88 push_hash_entry(&mut entries, "description", &self.description);
89 hash_entries(entries, hasher);
90 }
91}
92
93fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
94 let mut key_hasher = ComponentHasher::new();
95 key_hasher.put_string(key);
96 let mut value_hasher = ComponentHasher::new();
97 value.hash_component(&mut value_hasher);
98 entries.push(HashEntry::new(key_hasher, value_hasher));
99}
100
101fn hash_entries(mut entries: Vec<HashEntry>, hasher: &mut ComponentHasher) {
102 sort_map_entries(&mut entries);
103 hasher.start_map();
104 for entry in &entries {
105 hasher.put_raw_bytes(&entry.key_bytes);
106 hasher.put_raw_bytes(&entry.value_bytes);
107 }
108 hasher.end_map();
109}
110
111#[derive(Debug)]
113pub struct TrimMaterial {
114 pub key: Identifier,
115 value: TrimMaterialValue,
116}
117
118impl TrimMaterial {
119 #[must_use]
120 pub const fn new(key: Identifier, value: TrimMaterialValue) -> Self {
121 Self { key, value }
122 }
123
124 #[must_use]
125 pub const fn value(&self) -> &TrimMaterialValue {
126 &self.value
127 }
128}
129
130impl ToNbtTag for &TrimMaterial {
131 fn to_nbt_tag(self) -> NbtTag {
132 self.value.to_nbt_tag_ref()
133 }
134}
135
136pub type TrimMaterialRef = &'static TrimMaterial;
137
138pub struct TrimMaterialRegistry {
139 trim_materials_by_id: Vec<TrimMaterialRef>,
140 trim_materials_by_key: FxHashMap<Identifier, usize>,
141 tags: RegistryTags,
142 allows_registering: bool,
143}
144
145impl TrimMaterialRegistry {
146 #[must_use]
147 pub fn new() -> Self {
148 Self {
149 trim_materials_by_id: Vec::new(),
150 trim_materials_by_key: FxHashMap::default(),
151 tags: RegistryTags::default(),
152 allows_registering: true,
153 }
154 }
155}
156
157crate::impl_standard_methods!(
158 TrimMaterialRegistry,
159 TrimMaterialRef,
160 trim_materials_by_id,
161 trim_materials_by_key,
162 allows_registering
163);
164
165crate::impl_registry!(
166 TrimMaterialRegistry,
167 TrimMaterial,
168 trim_materials_by_id,
169 trim_materials_by_key,
170 trim_materials
171);
172crate::impl_tagged_registry!(TrimMaterialRegistry, trim_materials_by_key, "trim material");
173
174impl RegistryHolderEntry for TrimMaterial {
175 type Value = TrimMaterialValue;
176
177 const REGISTRY_NAME: &'static str = "trim material";
178
179 fn holder_value(&self) -> &Self::Value {
180 &self.value
181 }
182
183 fn holder_by_id(id: usize) -> Option<&'static Self> {
184 REGISTRY.trim_materials.by_id(id)
185 }
186
187 fn holder_by_key(key: &Identifier) -> Option<&'static Self> {
188 REGISTRY.trim_materials.by_key(key)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use steel_utils::Identifier;
195 use text_components::format::Color;
196
197 use crate::init_vanilla_registry;
198 use crate::{REGISTRY, vanilla_trim_materials};
199
200 #[test]
201 fn generated_materials_follow_vanilla_registry_order_and_palette_ids() {
202 init_vanilla_registry();
203 let keys = REGISTRY
204 .trim_materials
205 .iter()
206 .map(|(_, material)| material.key.path.as_ref())
207 .collect::<Vec<_>>();
208 assert_eq!(
209 keys,
210 [
211 "quartz",
212 "iron",
213 "netherite",
214 "redstone",
215 "copper",
216 "gold",
217 "emerald",
218 "diamond",
219 "lapis",
220 "amethyst",
221 "resin",
222 ]
223 );
224
225 let iron = vanilla_trim_materials::IRON.value();
226 assert_eq!(iron.palette_id(), &Identifier::vanilla_static("trim/iron"));
227 assert_eq!(
228 iron.description().format.color,
229 Some(Color::Rgb(0xec, 0xec, 0xec))
230 );
231 }
232
233 #[test]
234 fn generated_definition_uses_the_flattened_persistent_shape() {
235 use simdnbt::ToNbtTag as _;
236
237 init_vanilla_registry();
238
239 let simdnbt::owned::NbtTag::Compound(iron) = (&*vanilla_trim_materials::IRON).to_nbt_tag()
240 else {
241 panic!("trim material definition should encode as a compound");
242 };
243 assert_eq!(
244 iron.string("palette_id")
245 .map(|value| value.to_str().into_owned()),
246 Some("minecraft:trim/iron".to_owned())
247 );
248 assert_eq!(
249 iron.compound("description")
250 .and_then(|description| description.string("color"))
251 .map(|value| value.to_str().into_owned()),
252 Some("#ECECEC".to_owned())
253 );
254 }
255}