steel_core/entity/
synced_data.rs1use bitflags::bitflags;
2use steel_registry::{
3 entity_data::{DataValue, EntityPose},
4 vanilla_entity_data::VanillaEntityData,
5};
6use steel_utils::locks::SyncMutex;
7use text_components::TextComponent;
8
9bitflags! {
10 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12 struct EntitySharedFlags: u8 {
13 const ON_FIRE = 1 << 0;
14 const SHIFT_KEY_DOWN = 1 << 1;
15 const SPRINTING = 1 << 3;
16 const SWIMMING = 1 << 4;
17 const INVISIBLE = 1 << 5;
18 const GLOWING = 1 << 6;
19 const FALL_FLYING = 1 << 7;
20 }
21}
22
23impl EntitySharedFlags {
24 #[must_use]
25 const fn from_metadata_byte(byte: i8) -> Self {
26 Self::from_bits_retain(byte as u8)
27 }
28
29 #[must_use]
30 const fn metadata_byte(self) -> i8 {
31 self.bits() as i8
32 }
33}
34
35pub trait EntitySyncedData: Send + Sync {
37 fn pack_dirty(&self) -> Option<Vec<DataValue>>;
39
40 fn pack_all(&self) -> Vec<DataValue>;
42
43 fn is_no_gravity(&self) -> bool;
45
46 fn set_air_supply(&self, air_supply: i32);
48
49 fn set_custom_name(&self, custom_name: Option<TextComponent>);
51
52 fn set_custom_name_visible(&self, visible: bool);
54
55 fn set_silent(&self, silent: bool);
57
58 fn set_no_gravity(&self, no_gravity: bool);
60
61 fn set_pose(&self, pose: EntityPose);
63
64 fn is_shift_key_down(&self) -> bool;
66
67 fn is_swimming(&self) -> bool;
69
70 fn is_base_invisible_flag(&self) -> bool;
72
73 fn set_shift_key_down(&self, shift_key_down: bool);
75
76 fn set_swimming(&self, swimming: bool);
78
79 fn set_sprinting(&self, sprinting: bool);
81
82 fn set_fall_flying(&self, fall_flying: bool);
84
85 fn set_base_on_fire_flag(&self, on_fire: bool);
87
88 fn set_base_invisible_flag(&self, invisible: bool);
90
91 fn set_base_glowing_flag(&self, glowing: bool);
93
94 fn set_base_ticks_frozen(&self, ticks_frozen: i32);
96}
97
98impl<T> EntitySyncedData for SyncMutex<T>
99where
100 T: VanillaEntityData + Send + Sync,
101{
102 fn pack_dirty(&self) -> Option<Vec<DataValue>> {
103 VanillaEntityData::pack_dirty(&mut *self.lock())
104 }
105
106 fn pack_all(&self) -> Vec<DataValue> {
107 VanillaEntityData::pack_all(&*self.lock())
108 }
109
110 fn is_no_gravity(&self) -> bool {
111 *VanillaEntityData::base(&*self.lock()).no_gravity.get()
112 }
113
114 fn set_air_supply(&self, air_supply: i32) {
115 VanillaEntityData::base_mut(&mut *self.lock())
116 .air_supply
117 .set(air_supply);
118 }
119
120 fn set_custom_name(&self, custom_name: Option<TextComponent>) {
121 VanillaEntityData::base_mut(&mut *self.lock())
122 .custom_name
123 .set(custom_name.map(Box::new));
124 }
125
126 fn set_custom_name_visible(&self, visible: bool) {
127 VanillaEntityData::base_mut(&mut *self.lock())
128 .custom_name_visible
129 .set(visible);
130 }
131
132 fn set_silent(&self, silent: bool) {
133 VanillaEntityData::base_mut(&mut *self.lock())
134 .silent
135 .set(silent);
136 }
137
138 fn set_no_gravity(&self, no_gravity: bool) {
139 VanillaEntityData::base_mut(&mut *self.lock())
140 .no_gravity
141 .set(no_gravity);
142 }
143
144 fn set_pose(&self, pose: EntityPose) {
145 VanillaEntityData::base_mut(&mut *self.lock())
146 .pose
147 .set(pose);
148 }
149
150 fn is_shift_key_down(&self) -> bool {
151 EntitySharedFlags::from_metadata_byte(
152 *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
153 )
154 .contains(EntitySharedFlags::SHIFT_KEY_DOWN)
155 }
156
157 fn is_swimming(&self) -> bool {
158 EntitySharedFlags::from_metadata_byte(
159 *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
160 )
161 .contains(EntitySharedFlags::SWIMMING)
162 }
163
164 fn is_base_invisible_flag(&self) -> bool {
165 EntitySharedFlags::from_metadata_byte(
166 *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
167 )
168 .contains(EntitySharedFlags::INVISIBLE)
169 }
170
171 fn set_shift_key_down(&self, shift_key_down: bool) {
172 self.set_shared_flag(EntitySharedFlags::SHIFT_KEY_DOWN, shift_key_down);
173 }
174
175 fn set_swimming(&self, swimming: bool) {
176 self.set_shared_flag(EntitySharedFlags::SWIMMING, swimming);
177 }
178
179 fn set_sprinting(&self, sprinting: bool) {
180 self.set_shared_flag(EntitySharedFlags::SPRINTING, sprinting);
181 }
182
183 fn set_fall_flying(&self, fall_flying: bool) {
184 self.set_shared_flag(EntitySharedFlags::FALL_FLYING, fall_flying);
185 }
186
187 fn set_base_on_fire_flag(&self, on_fire: bool) {
188 self.set_shared_flag(EntitySharedFlags::ON_FIRE, on_fire);
189 }
190
191 fn set_base_invisible_flag(&self, invisible: bool) {
192 self.set_shared_flag(EntitySharedFlags::INVISIBLE, invisible);
193 }
194
195 fn set_base_glowing_flag(&self, glowing: bool) {
196 self.set_shared_flag(EntitySharedFlags::GLOWING, glowing);
197 }
198
199 fn set_base_ticks_frozen(&self, ticks_frozen: i32) {
200 VanillaEntityData::base_mut(&mut *self.lock())
201 .ticks_frozen
202 .set(ticks_frozen);
203 }
204}
205
206trait SharedFlagSetter {
207 fn set_shared_flag(&self, flag: EntitySharedFlags, value: bool);
208}
209
210impl<T> SharedFlagSetter for SyncMutex<T>
211where
212 T: VanillaEntityData + Send + Sync,
213{
214 fn set_shared_flag(&self, flag: EntitySharedFlags, value: bool) {
215 let mut entity_data = self.lock();
216 let base = VanillaEntityData::base_mut(&mut *entity_data);
217 let mut flags = EntitySharedFlags::from_metadata_byte(*base.shared_flags.get());
218 flags.set(flag, value);
219 base.shared_flags.set(flags.metadata_byte());
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use steel_registry::{entity_data::EntityData, vanilla_entity_data::ItemEntityData};
226 use text_components::TextComponent;
227
228 use super::*;
229
230 #[test]
231 fn synced_data_reads_no_gravity_from_generated_base_layer() {
232 let data = SyncMutex::new(ItemEntityData::new());
233 assert!(!EntitySyncedData::is_no_gravity(&data));
234
235 EntitySyncedData::set_no_gravity(&data, true);
236
237 assert!(EntitySyncedData::is_no_gravity(&data));
238 let Some(values) = EntitySyncedData::pack_dirty(&data) else {
239 panic!("expected dirty no-gravity metadata");
240 };
241 assert_eq!(values.len(), 1);
242 assert_eq!(values[0].index, 5);
243 assert_eq!(values[0].serializer_id, 8);
244 assert!(matches!(values[0].value, EntityData::Boolean(true)));
245 assert!(EntitySyncedData::pack_dirty(&data).is_none());
246 }
247
248 #[test]
249 fn synced_data_reads_shift_key_down_from_generated_base_layer() {
250 let data = SyncMutex::new(ItemEntityData::new());
251 assert!(!EntitySyncedData::is_shift_key_down(&data));
252
253 data.lock()
254 .base_mut()
255 .shared_flags
256 .set(EntitySharedFlags::SHIFT_KEY_DOWN.metadata_byte());
257
258 assert!(EntitySyncedData::is_shift_key_down(&data));
259 }
260
261 #[test]
262 fn synced_data_reads_swimming_from_generated_base_layer() {
263 let data = SyncMutex::new(ItemEntityData::new());
264 assert!(!EntitySyncedData::is_swimming(&data));
265
266 data.lock()
267 .base_mut()
268 .shared_flags
269 .set(EntitySharedFlags::SWIMMING.metadata_byte());
270
271 assert!(EntitySyncedData::is_swimming(&data));
272 }
273
274 #[test]
275 fn synced_data_reads_invisible_from_generated_base_layer() {
276 let data = SyncMutex::new(ItemEntityData::new());
277 assert!(!EntitySyncedData::is_base_invisible_flag(&data));
278
279 EntitySyncedData::set_base_invisible_flag(&data, true);
280
281 assert!(EntitySyncedData::is_base_invisible_flag(&data));
282 }
283
284 #[test]
285 fn synced_data_writes_individual_shared_flags_without_stomping() {
286 let data = SyncMutex::new(ItemEntityData::new());
287
288 EntitySyncedData::set_shift_key_down(&data, true);
289 EntitySyncedData::set_swimming(&data, true);
290 EntitySyncedData::set_sprinting(&data, true);
291 EntitySyncedData::set_fall_flying(&data, true);
292
293 let flags = EntitySharedFlags::from_metadata_byte(*data.lock().base().shared_flags.get());
294 assert!(flags.contains(EntitySharedFlags::SHIFT_KEY_DOWN));
295 assert!(flags.contains(EntitySharedFlags::SWIMMING));
296 assert!(flags.contains(EntitySharedFlags::SPRINTING));
297 assert!(flags.contains(EntitySharedFlags::FALL_FLYING));
298
299 EntitySyncedData::set_swimming(&data, false);
300
301 let flags = EntitySharedFlags::from_metadata_byte(*data.lock().base().shared_flags.get());
302 assert!(flags.contains(EntitySharedFlags::SHIFT_KEY_DOWN));
303 assert!(!flags.contains(EntitySharedFlags::SWIMMING));
304 assert!(flags.contains(EntitySharedFlags::SPRINTING));
305 assert!(flags.contains(EntitySharedFlags::FALL_FLYING));
306 }
307
308 #[test]
309 fn synced_data_writes_fire_and_freeze_base_layer() {
310 let data = SyncMutex::new(ItemEntityData::new());
311
312 data.set_base_on_fire_flag(true);
313 data.set_base_ticks_frozen(12);
314
315 let values =
316 EntitySyncedData::pack_dirty(&data).expect("expected dirty base fire/freeze metadata");
317 assert_eq!(values.len(), 2);
318 assert!(matches!(values[0].value, EntityData::Byte(1)));
319 assert!(matches!(values[1].value, EntityData::Int(12)));
320
321 assert!(EntitySyncedData::pack_dirty(&data).is_none());
322 }
323
324 #[test]
325 fn synced_data_writes_shared_save_base_layer() {
326 let data = SyncMutex::new(ItemEntityData::new());
327
328 data.set_air_supply(42);
329 data.set_custom_name(Some(TextComponent::plain("Steel")));
330 data.set_custom_name_visible(true);
331 data.set_silent(true);
332 data.set_base_glowing_flag(true);
333
334 let values =
335 EntitySyncedData::pack_dirty(&data).expect("expected dirty shared save metadata");
336
337 assert_eq!(values.len(), 5);
338 assert_eq!(values[0].index, 0);
339 assert!(matches!(
340 values[0].value,
341 EntityData::Byte(value)
342 if EntitySharedFlags::from_metadata_byte(value)
343 .contains(EntitySharedFlags::GLOWING)
344 ));
345 assert_eq!(values[1].index, 1);
346 assert!(matches!(values[1].value, EntityData::Int(42)));
347 assert_eq!(values[2].index, 2);
348 assert!(matches!(
349 values[2].value,
350 EntityData::OptionalComponent(Some(_))
351 ));
352 assert_eq!(values[3].index, 3);
353 assert!(matches!(values[3].value, EntityData::Boolean(true)));
354 assert_eq!(values[4].index, 4);
355 assert!(matches!(values[4].value, EntityData::Boolean(true)));
356 }
357}