azalea_entity/plugin/
relative_updates.rs

1// How entity updates are processed (to avoid issues with shared worlds)
2// - each bot contains a map of { entity id: updates received }
3// - the shared world also contains a canonical "true" updates received for each
4//   entity
5// - when a client loads an entity, its "updates received" is set to the same as
6//   the global "updates received"
7// - when the shared world sees an entity for the first time, the "updates
8//   received" is set to 1.
9// - clients can force the shared "updates received" to 0 to make it so certain
10//   entities (i.e. other bots in our swarm) don't get confused and updated by
11//   other bots
12// - when a client gets an update to an entity, we check if our "updates
13//   received" is the same as the shared world's "updates received": if it is,
14//   then process the update and increment the client's and shared world's
15//   "updates received" if not, then we simply increment our local "updates
16//   received" and do nothing else
17
18use std::sync::Arc;
19
20use azalea_core::entity_id::MinecraftEntityId;
21use azalea_world::PartialWorld;
22use bevy_ecs::prelude::*;
23use derive_more::{Deref, DerefMut};
24use parking_lot::RwLock;
25use tracing::warn;
26
27use crate::LocalEntity;
28
29/// An [`EntityCommand`] that applies a "relative update" to an entity, which
30/// means this update won't be run multiple times by different clients in the
31/// same world.
32///
33/// This is used to avoid a bug where when there's multiple clients in the same
34/// world and an entity sends a relative move packet to all clients, its
35/// position gets desynced since the relative move is applied multiple times.
36///
37/// Don't use this unless you actually got an entity update packet that all
38/// other clients within render distance will get too. You usually don't need
39/// this when the change isn't relative either.
40pub struct RelativeEntityUpdate {
41    pub partial_world: Arc<RwLock<PartialWorld>>,
42    // a function that takes the entity and updates it
43    pub update: Box<dyn FnOnce(&mut EntityWorldMut) + Send + Sync>,
44}
45impl RelativeEntityUpdate {
46    pub fn new(
47        partial_world: Arc<RwLock<PartialWorld>>,
48        update: impl FnOnce(&mut EntityWorldMut) + Send + Sync + 'static,
49    ) -> Self {
50        Self {
51            partial_world,
52            update: Box::new(update),
53        }
54    }
55}
56
57/// A component that counts the number of times this entity has been modified.
58///
59/// This is used for making sure two clients don't do the same relative update
60/// on an entity.
61///
62/// If an entity is local (i.e. it's a client/LocalEntity), this component
63/// should NOT be present in the entity.
64#[derive(Component, Debug, Deref, DerefMut)]
65pub struct UpdatesReceived(u32);
66
67impl EntityCommand for RelativeEntityUpdate {
68    fn apply(self, mut entity: EntityWorldMut) {
69        let partial_entity_infos = &mut self.partial_world.write().entity_infos;
70
71        if Some(entity.id()) == partial_entity_infos.owner_entity {
72            // if the entity owns this partial world, it's always allowed to update itself
73            (self.update)(&mut entity);
74            return;
75        };
76
77        let entity_id = *entity.get::<MinecraftEntityId>().unwrap();
78        if entity.contains::<LocalEntity>() {
79            // a client tried to update another client, which isn't allowed
80            return;
81        }
82
83        let this_client_updates_received = partial_entity_infos
84            .updates_received
85            .get(&entity_id)
86            .copied();
87
88        let can_update = if let Some(updates_received) = entity.get::<UpdatesReceived>() {
89            this_client_updates_received.unwrap_or(1) == **updates_received
90        } else {
91            // no UpdatesReceived means the entity was just spawned
92            true
93        };
94        if can_update {
95            let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
96            partial_entity_infos
97                .updates_received
98                .insert(entity_id, new_updates_received);
99
100            entity.insert(UpdatesReceived(new_updates_received));
101
102            (self.update)(&mut entity);
103        }
104    }
105}
106
107/// A system that logs a warning if an entity has both [`UpdatesReceived`]
108/// and [`LocalEntity`].
109pub fn debug_detect_updates_received_on_local_entities(
110    query: Query<Entity, (With<LocalEntity>, With<UpdatesReceived>)>,
111) {
112    for entity in &query {
113        warn!("Entity {entity:?} has both LocalEntity and UpdatesReceived");
114    }
115}