Skip to main content

steel_core/entity/
synced_data.rs

1use 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    /// Vanilla base entity shared-flags metadata byte.
11    #[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
35/// Thread-safe access to an entity's vanilla synchronized data.
36pub trait EntitySyncedData: Send + Sync {
37    /// Packs dirty values for network sync, clearing dirty flags.
38    fn pack_dirty(&self) -> Option<Vec<DataValue>>;
39
40    /// Packs all non-default values for initial entity spawn.
41    fn pack_all(&self) -> Vec<DataValue>;
42
43    /// Returns the shared vanilla `NoGravity` flag.
44    fn is_no_gravity(&self) -> bool;
45
46    /// Sets synchronized vanilla air supply.
47    fn set_air_supply(&self, air_supply: i32);
48
49    /// Sets synchronized vanilla custom name.
50    fn set_custom_name(&self, custom_name: Option<TextComponent>);
51
52    /// Sets synchronized vanilla custom-name visibility.
53    fn set_custom_name_visible(&self, visible: bool);
54
55    /// Sets synchronized vanilla silent flag.
56    fn set_silent(&self, silent: bool);
57
58    /// Sets the shared vanilla `NoGravity` flag.
59    fn set_no_gravity(&self, no_gravity: bool);
60
61    /// Sets synchronized vanilla pose.
62    fn set_pose(&self, pose: EntityPose);
63
64    /// Returns the shared vanilla shift-key-down flag.
65    fn is_shift_key_down(&self) -> bool;
66
67    /// Returns the shared vanilla swimming flag.
68    fn is_swimming(&self) -> bool;
69
70    /// Returns the shared vanilla invisible flag.
71    fn is_base_invisible_flag(&self) -> bool;
72
73    /// Sets the shared vanilla shift-key-down flag.
74    fn set_shift_key_down(&self, shift_key_down: bool);
75
76    /// Sets the shared vanilla swimming flag.
77    fn set_swimming(&self, swimming: bool);
78
79    /// Sets the shared vanilla sprinting flag.
80    fn set_sprinting(&self, sprinting: bool);
81
82    /// Sets the shared vanilla fall-flying flag.
83    fn set_fall_flying(&self, fall_flying: bool);
84
85    /// Sets the shared vanilla on-fire flag.
86    fn set_base_on_fire_flag(&self, on_fire: bool);
87
88    /// Sets the shared vanilla invisible flag.
89    fn set_base_invisible_flag(&self, invisible: bool);
90
91    /// Sets the shared vanilla glowing flag.
92    fn set_base_glowing_flag(&self, glowing: bool);
93
94    /// Sets synchronized vanilla frozen ticks.
95    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}