steel_registry/data_components/components/
bees.rs1use std::io::{Cursor, Error, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
9use steel_utils::nbt::NbtNumeric as _;
10use steel_utils::serial::{ReadFrom, WriteTo};
11
12use super::EntityData;
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct BeehiveOccupant {
17 entity_data: EntityData,
18 ticks_in_hive: i32,
19 min_ticks_in_hive: i32,
20}
21
22impl BeehiveOccupant {
23 #[must_use]
24 pub const fn new(entity_data: EntityData, ticks_in_hive: i32, min_ticks_in_hive: i32) -> Self {
25 Self {
26 entity_data,
27 ticks_in_hive,
28 min_ticks_in_hive,
29 }
30 }
31
32 #[must_use]
33 pub const fn entity_data(&self) -> &EntityData {
34 &self.entity_data
35 }
36
37 #[must_use]
38 pub const fn ticks_in_hive(&self) -> i32 {
39 self.ticks_in_hive
40 }
41
42 #[must_use]
43 pub const fn min_ticks_in_hive(&self) -> i32 {
44 self.min_ticks_in_hive
45 }
46
47 fn to_nbt_compound(&self) -> NbtCompound {
48 let mut compound = NbtCompound::new();
49 compound.insert("entity_data", self.entity_data.clone().to_nbt_tag());
50 compound.insert("ticks_in_hive", self.ticks_in_hive);
51 compound.insert("min_ticks_in_hive", self.min_ticks_in_hive);
52 compound
53 }
54
55 fn from_nbt_compound(compound: &NbtCompound) -> Option<Self> {
56 let entity_data = EntityData::from_owned_nbt(compound.get("entity_data")?)?;
57 let ticks_in_hive = compound.get("ticks_in_hive")?.codec_i32()?;
58 let min_ticks_in_hive = compound.get("min_ticks_in_hive")?.codec_i32()?;
59 Some(Self::new(entity_data, ticks_in_hive, min_ticks_in_hive))
60 }
61}
62
63impl WriteTo for BeehiveOccupant {
64 fn write(&self, writer: &mut impl Write) -> Result<()> {
65 self.entity_data.write(writer)?;
66 VarInt(self.ticks_in_hive).write(writer)?;
67 VarInt(self.min_ticks_in_hive).write(writer)
68 }
69}
70
71impl ReadFrom for BeehiveOccupant {
72 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
73 Ok(Self::new(
74 EntityData::read(data)?,
75 VarInt::read(data)?.0,
76 VarInt::read(data)?.0,
77 ))
78 }
79}
80
81impl HashComponent for BeehiveOccupant {
82 fn hash_component(&self, hasher: &mut ComponentHasher) {
83 let mut entries = Vec::with_capacity(3);
84 push_hash_entry(&mut entries, "entity_data", &self.entity_data);
85 push_hash_entry(&mut entries, "ticks_in_hive", &self.ticks_in_hive);
86 push_hash_entry(&mut entries, "min_ticks_in_hive", &self.min_ticks_in_hive);
87 sort_map_entries(&mut entries);
88 hasher.start_map();
89 for entry in &entries {
90 hasher.put_raw_bytes(&entry.key_bytes);
91 hasher.put_raw_bytes(&entry.value_bytes);
92 }
93 hasher.end_map();
94 }
95}
96
97#[derive(Debug, Default, Clone, PartialEq)]
99pub struct Bees {
100 bees: Vec<BeehiveOccupant>,
101}
102
103impl Bees {
104 #[must_use]
105 pub const fn empty() -> Self {
106 Self { bees: Vec::new() }
107 }
108
109 #[must_use]
110 pub const fn new(bees: Vec<BeehiveOccupant>) -> Self {
111 Self { bees }
112 }
113
114 #[must_use]
115 pub fn bees(&self) -> &[BeehiveOccupant] {
116 &self.bees
117 }
118}
119
120impl WriteTo for Bees {
121 fn write(&self, writer: &mut impl Write) -> Result<()> {
122 write_count(self.bees.len(), writer)?;
123 for bee in &self.bees {
124 bee.write(writer)?;
125 }
126 Ok(())
127 }
128}
129
130impl ReadFrom for Bees {
131 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
132 let count = read_count(data)?;
133 let mut bees = Vec::with_capacity(count.min(65_536));
134 for _ in 0..count {
135 bees.push(BeehiveOccupant::read(data)?);
136 }
137 Ok(Self::new(bees))
138 }
139}
140
141impl ToNbtTag for Bees {
142 fn to_nbt_tag(self) -> NbtTag {
143 if self.bees.is_empty() {
144 NbtTag::List(NbtList::Empty)
145 } else {
146 NbtTag::List(NbtList::Compound(
147 self.bees
148 .iter()
149 .map(BeehiveOccupant::to_nbt_compound)
150 .collect(),
151 ))
152 }
153 }
154}
155
156impl FromNbtTag for Bees {
157 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
158 let values = tag.list()?.to_owned().as_nbt_tags();
159 let bees = values
160 .iter()
161 .map(|tag| BeehiveOccupant::from_nbt_compound(tag.compound()?))
162 .collect::<Option<Vec<_>>>()?;
163 Some(Self::new(bees))
164 }
165}
166
167impl HashComponent for Bees {
168 fn hash_component(&self, hasher: &mut ComponentHasher) {
169 hasher.start_list();
170 for bee in &self.bees {
171 hasher.put_component_hash(bee);
172 }
173 hasher.end_list();
174 }
175}
176
177fn write_count(count: usize, writer: &mut impl Write) -> Result<()> {
178 let count = i32::try_from(count)
179 .map_err(|_| Error::other("Bee occupant list exceeds protocol range"))?;
180 VarInt(count).write(writer)
181}
182
183fn read_count(data: &mut Cursor<&[u8]>) -> Result<usize> {
184 let count = VarInt::read(data)?.0;
185 usize::try_from(count).map_err(|_| Error::other(format!("Negative bee count: {count}")))
186}
187
188fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
189 let mut key_hasher = ComponentHasher::new();
190 key_hasher.put_string(key);
191 let mut value_hasher = ComponentHasher::new();
192 value.hash_component(&mut value_hasher);
193 entries.push(HashEntry::new(key_hasher, value_hasher));
194}
195
196#[cfg(test)]
197mod tests {
198 use std::io::Cursor;
199
200 use simdnbt::owned::{NbtCompound, NbtTag};
201 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
202 use steel_utils::hash::HashComponent as _;
203 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
204
205 use super::{BeehiveOccupant, Bees};
206 use crate::data_components::components::{CustomData, EntityData};
207 use crate::data_components::vanilla_components::BEES;
208 use crate::init_vanilla_registry;
209 use crate::{REGISTRY, RegistryExt};
210
211 fn parse(tag: NbtTag) -> Option<Bees> {
212 let mut bytes = Vec::new();
213 tag.write(&mut bytes);
214 let borrowed = simdnbt::borrow::read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
215 Bees::from_nbt_tag(borrowed.as_tag())
216 }
217
218 #[test]
219 fn bee_occupants_round_trip_both_codecs_and_hash_as_a_list() {
220 init_vanilla_registry();
221 let bee = REGISTRY
222 .entity_types
223 .by_key(&steel_utils::Identifier::vanilla_static("bee"))
224 .expect("bee should be registered");
225 let mut payload = NbtCompound::new();
226 payload.insert("HasNectar", true);
227 let value = Bees::new(vec![BeehiveOccupant::new(
228 EntityData::new(
229 bee,
230 CustomData::try_from_compound(payload).expect("valid bee data"),
231 ),
232 17,
233 600,
234 )]);
235 let nbt = value.clone().to_nbt_tag();
236 assert_eq!(parse(nbt.clone()), Some(value.clone()));
237 assert_eq!(value.compute_hash(), nbt.compute_hash());
238
239 let mut network = Vec::new();
240 value.write(&mut network).expect("bees should encode");
241 assert_eq!(
242 Bees::read(&mut Cursor::new(network.as_slice())).expect("bees should decode"),
243 value
244 );
245 }
246
247 #[test]
248 fn extracted_beehives_start_with_no_occupants() {
249 init_vanilla_registry();
250 for key in ["bee_nest", "beehive"] {
251 let item = REGISTRY
252 .items
253 .by_key(&steel_utils::Identifier::vanilla(key.to_owned()))
254 .unwrap_or_else(|| panic!("{key} should be registered"));
255 assert_eq!(item.components.get(BEES), Some(Bees::empty()));
256 }
257 }
258}