valence_entity/
tracked_data.rs

1use bevy_ecs::prelude::*;
2use tracing::warn;
3use valence_protocol::Encode;
4
5/// Cache for all the tracked data of an entity. Used for the
6/// [`EntityTrackerUpdateS2c`][packet] packet.
7///
8/// [packet]: valence_protocol::packets::play::EntityTrackerUpdateS2c
9#[derive(Component, Default, Debug)]
10pub struct TrackedData {
11    init_data: Vec<u8>,
12    /// A map of tracked data indices to the byte length of the entry in
13    /// `init_data`.
14    init_entries: Vec<(u8, u32)>,
15    update_data: Vec<u8>,
16}
17
18impl TrackedData {
19    /// Returns initial tracked data for the entity, ready to be sent in the
20    /// [`EntityTrackerUpdateS2c`][packet] packet. This is used when the entity
21    /// enters the view of a client.
22    ///
23    /// [packet]: valence_protocol::packets::play::EntityTrackerUpdateS2c
24    pub fn init_data(&self) -> Option<&[u8]> {
25        (self.init_data.len() > 1).then_some(&self.init_data)
26    }
27
28    /// Contains updated tracked data for the entity, ready to be sent in the
29    /// [`EntityTrackerUpdateS2c`][packet] packet. This is used when tracked
30    /// data is changed and the client is already in view of the entity.
31    ///
32    /// [packet]: valence_protocol::packets::play::EntityTrackerUpdateS2c
33    pub fn update_data(&self) -> Option<&[u8]> {
34        (self.update_data.len() > 1).then_some(&self.update_data)
35    }
36
37    pub fn insert_init_value<V: Encode>(&mut self, index: u8, type_id: u8, value: V) {
38        debug_assert!(
39            index != 0xff,
40            "index of 0xff is reserved for the terminator"
41        );
42
43        self.remove_init_value(index);
44
45        self.init_data.pop(); // Remove terminator.
46
47        // Append the new value to the end.
48        let len_before = self.init_data.len();
49
50        self.init_data.extend_from_slice(&[index, type_id]);
51        if let Err(e) = value.encode(&mut self.init_data) {
52            warn!("failed to encode initial tracked data: {e:#}");
53        }
54
55        let len = self.init_data.len() - len_before;
56
57        self.init_entries.push((index, len as u32));
58
59        self.init_data.push(0xff); // Add terminator.
60    }
61
62    pub fn remove_init_value(&mut self, index: u8) -> bool {
63        let mut start = 0;
64
65        for (pos, &(idx, len)) in self.init_entries.iter().enumerate() {
66            if idx == index {
67                let end = start + len as usize;
68
69                self.init_data.drain(start..end);
70                self.init_entries.remove(pos);
71
72                return true;
73            }
74
75            start += len as usize;
76        }
77
78        false
79    }
80
81    pub fn append_update_value<V: Encode>(&mut self, index: u8, type_id: u8, value: V) {
82        debug_assert!(
83            index != 0xff,
84            "index of 0xff is reserved for the terminator"
85        );
86
87        self.update_data.pop(); // Remove terminator.
88
89        self.update_data.extend_from_slice(&[index, type_id]);
90        if let Err(e) = value.encode(&mut self.update_data) {
91            warn!("failed to encode updated tracked data: {e:#}");
92        }
93
94        self.update_data.push(0xff); // Add terminator.
95    }
96
97    pub fn clear_update_values(&mut self) {
98        self.update_data.clear();
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn insert_remove_init_tracked_data() {
108        let mut td = TrackedData::default();
109
110        td.insert_init_value(0, 3, "foo");
111        td.insert_init_value(10, 6, "bar");
112        td.insert_init_value(5, 9, "baz");
113
114        assert!(td.remove_init_value(10));
115        assert!(!td.remove_init_value(10));
116
117        // Insertion overwrites value at index 0.
118        td.insert_init_value(0, 64, "quux");
119
120        assert!(td.remove_init_value(0));
121        assert!(td.remove_init_value(5));
122
123        assert!(td.init_data.as_slice().is_empty() || td.init_data.as_slice() == [0xff]);
124        assert!(td.init_data().is_none());
125
126        assert!(td.update_data.is_empty());
127    }
128}