steel_registry/data_components/components/
debug_stick_state.rs1use std::collections::BTreeMap;
4use std::io::{Cursor, Error, Result, Write};
5use std::str::FromStr;
6
7use simdnbt::owned::{NbtCompound, NbtTag, read_tag};
8use simdnbt::{FromNbtTag, ToNbtTag};
9use steel_utils::Identifier;
10use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
11use steel_utils::nbt::vanilla_nbt_heap_size;
12use steel_utils::serial::{ReadFrom, WriteTo};
13
14use crate::blocks::BlockRef;
15use crate::{REGISTRY, RegistryExt};
16
17const DEFAULT_NBT_QUOTA: u64 = 2_097_152;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct DebugStickProperty {
22 block: BlockRef,
23 property: &'static str,
24}
25
26impl DebugStickProperty {
27 #[must_use]
28 pub const fn block(&self) -> BlockRef {
29 self.block
30 }
31
32 #[must_use]
33 pub const fn property(&self) -> &'static str {
34 self.property
35 }
36}
37
38#[derive(Debug, Default, Clone, PartialEq, Eq)]
40pub struct DebugStickState {
41 properties: BTreeMap<String, DebugStickProperty>,
42}
43
44impl DebugStickState {
45 #[must_use]
46 pub const fn empty() -> Self {
47 Self {
48 properties: BTreeMap::new(),
49 }
50 }
51
52 pub fn new(entries: impl IntoIterator<Item = (BlockRef, String)>) -> Result<Self> {
53 let mut properties = BTreeMap::new();
54 for (block, name) in entries {
55 let property = block
56 .properties
57 .iter()
58 .find(|property| property.get_name() == name)
59 .ok_or_else(|| Error::other(format!("Block {} has no property {name}", block.key)))?
60 .get_name();
61 properties.insert(
62 block.key.to_string(),
63 DebugStickProperty { block, property },
64 );
65 }
66 Ok(Self { properties })
67 }
68
69 pub fn properties(&self) -> impl Iterator<Item = &DebugStickProperty> {
70 self.properties.values()
71 }
72
73 fn to_nbt_tag_ref(&self) -> NbtTag {
74 let mut compound = NbtCompound::new();
75 for property in self.properties.values() {
76 compound.insert(property.block.key.to_string(), property.property);
77 }
78 NbtTag::Compound(compound)
79 }
80
81 fn from_owned_nbt(tag: &NbtTag) -> Option<Self> {
82 let compound = tag.compound()?;
83 let mut entries = Vec::with_capacity(compound.len());
84 for (key, value) in compound.iter() {
85 let key = Identifier::from_str(&key.to_string()).ok()?;
86 let block = REGISTRY.blocks.by_key(&key)?;
87 entries.push((block, value.string()?.to_string()));
88 }
89 Self::new(entries).ok()
90 }
91}
92
93impl WriteTo for DebugStickState {
94 fn write(&self, writer: &mut impl Write) -> Result<()> {
95 let mut encoded = Vec::new();
96 self.to_nbt_tag_ref().write(&mut encoded);
97 writer.write_all(&encoded)
98 }
99}
100
101impl ReadFrom for DebugStickState {
102 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
103 let tag =
104 read_tag(data).map_err(|error| Error::other(format!("Invalid NBT: {error:?}")))?;
105 let Some(heap_size) = vanilla_nbt_heap_size(&tag) else {
106 return Err(Error::other("NBT contains malformed modified UTF-8"));
107 };
108 if heap_size > DEFAULT_NBT_QUOTA {
109 return Err(Error::other(format!(
110 "NBT exceeds Vanilla's {DEFAULT_NBT_QUOTA}-byte heap quota"
111 )));
112 }
113 Self::from_owned_nbt(&tag)
114 .ok_or_else(|| Error::other("Debug stick state network value is malformed"))
115 }
116}
117
118impl ToNbtTag for DebugStickState {
119 fn to_nbt_tag(self) -> NbtTag {
120 self.to_nbt_tag_ref()
121 }
122}
123
124impl FromNbtTag for DebugStickState {
125 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
126 Self::from_owned_nbt(&tag.to_owned())
127 }
128}
129
130impl HashComponent for DebugStickState {
131 fn hash_component(&self, hasher: &mut ComponentHasher) {
132 let mut entries = Vec::with_capacity(self.properties.len());
133 for property in self.properties.values() {
134 let mut key_hasher = ComponentHasher::new();
135 key_hasher.put_string(&property.block.key.to_string());
136 let mut value_hasher = ComponentHasher::new();
137 value_hasher.put_string(property.property);
138 entries.push(HashEntry::new(key_hasher, value_hasher));
139 }
140 sort_map_entries(&mut entries);
141 hasher.start_map();
142 for entry in &entries {
143 hasher.put_raw_bytes(&entry.key_bytes);
144 hasher.put_raw_bytes(&entry.value_bytes);
145 }
146 hasher.end_map();
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use std::io::Cursor;
153
154 use simdnbt::owned::{NbtCompound, NbtTag};
155 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
156 use steel_utils::hash::HashComponent as _;
157 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
158
159 use super::DebugStickState;
160 use crate::data_components::vanilla_components::DEBUG_STICK_STATE;
161 use crate::init_vanilla_registry;
162 use crate::{REGISTRY, RegistryExt, vanilla_blocks};
163
164 fn parse(tag: NbtTag) -> Option<DebugStickState> {
165 let mut bytes = Vec::new();
166 tag.write(&mut bytes);
167 let borrowed = simdnbt::borrow::read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
168 DebugStickState::from_nbt_tag(borrowed.as_tag())
169 }
170
171 #[test]
172 fn block_property_map_validates_properties_and_uses_codec_derived_network() {
173 init_vanilla_registry();
174 let value = DebugStickState::new([(&vanilla_blocks::REDSTONE_WIRE, "power".to_owned())])
175 .expect("redstone wire has a power property");
176 let mut compound = NbtCompound::new();
177 compound.insert("minecraft:redstone_wire", "power");
178 let nbt = NbtTag::Compound(compound);
179 assert_eq!(value.clone().to_nbt_tag(), nbt);
180 assert_eq!(parse(nbt.clone()), Some(value.clone()));
181 assert_eq!(value.compute_hash(), nbt.compute_hash());
182
183 let mut network = Vec::new();
184 value
185 .write(&mut network)
186 .expect("debug state should encode");
187 assert_eq!(
188 DebugStickState::read(&mut Cursor::new(network.as_slice()))
189 .expect("debug state should decode"),
190 value
191 );
192 assert!(DebugStickState::new([(&vanilla_blocks::STONE, "missing".to_owned())]).is_err());
193 }
194
195 #[test]
196 fn extracted_debug_stick_has_an_empty_state() {
197 init_vanilla_registry();
198 let item = REGISTRY
199 .items
200 .by_key(&steel_utils::Identifier::vanilla_static("debug_stick"))
201 .expect("debug stick should be registered");
202 assert_eq!(
203 item.components.get(DEBUG_STICK_STATE),
204 Some(DebugStickState::empty())
205 );
206 }
207}