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_world::{MinecraftEntityId, PartialInstance};
21use bevy_ecs::{
22 prelude::{Component, Entity},
23 query::With,
24 system::{EntityCommand, Query},
25 world::{EntityWorldMut, World},
26};
27use derive_more::{Deref, DerefMut};
28use parking_lot::RwLock;
29use tracing::warn;
30
31use crate::LocalEntity;
32
33/// An [`EntityCommand`] that applies a "relative update" to an entity, which
34/// means this update won't be run multiple times by different clients in the
35/// same world.
36///
37/// This is used to avoid a bug where when there's multiple clients in the same
38/// world and an entity sends a relative move packet to all clients, its
39/// position gets desynced since the relative move is applied multiple times.
40///
41/// Don't use this unless you actually got an entity update packet that all
42/// other clients within render distance will get too. You usually don't need
43/// this when the change isn't relative either.
44pub struct RelativeEntityUpdate {
45 pub partial_world: Arc<RwLock<PartialInstance>>,
46 // a function that takes the entity and updates it
47 pub update: Box<dyn FnOnce(&mut EntityWorldMut) + Send + Sync>,
48}
49impl RelativeEntityUpdate {
50 pub fn new(
51 partial_world: Arc<RwLock<PartialInstance>>,
52 update: impl FnOnce(&mut EntityWorldMut) + Send + Sync + 'static,
53 ) -> Self {
54 Self {
55 partial_world,
56 update: Box::new(update),
57 }
58 }
59}
60
61/// A component that counts the number of times this entity has been modified.
62/// This is used for making sure two clients don't do the same relative update
63/// on an entity.
64///
65/// If an entity is local (i.e. it's a client/LocalEntity), this component
66/// should NOT be present in the entity.
67#[derive(Component, Debug, Deref, DerefMut)]
68pub struct UpdatesReceived(u32);
69
70impl EntityCommand for RelativeEntityUpdate {
71 fn apply(self, entity: Entity, world: &mut World) {
72 let partial_entity_infos = &mut self.partial_world.write().entity_infos;
73
74 let mut entity_mut = world.entity_mut(entity);
75
76 if Some(entity) == partial_entity_infos.owner_entity {
77 // if the entity owns this partial world, it's always allowed to update itself
78 (self.update)(&mut entity_mut);
79 return;
80 };
81
82 let entity_id = *entity_mut.get::<MinecraftEntityId>().unwrap();
83 if entity_mut.contains::<LocalEntity>() {
84 // a client tried to update another client, which isn't allowed
85 return;
86 }
87
88 let this_client_updates_received = partial_entity_infos
89 .updates_received
90 .get(&entity_id)
91 .copied();
92
93 let can_update = if let Some(updates_received) = entity_mut.get::<UpdatesReceived>() {
94 this_client_updates_received.unwrap_or(1) == **updates_received
95 } else {
96 // no UpdatesReceived means the entity was just spawned
97 true
98 };
99 if can_update {
100 let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
101 partial_entity_infos
102 .updates_received
103 .insert(entity_id, new_updates_received);
104
105 entity_mut.insert(UpdatesReceived(new_updates_received));
106
107 let mut entity = world.entity_mut(entity);
108 (self.update)(&mut entity);
109 }
110 }
111}
112
113/// The [`UpdatesReceived`] component should never be on [`LocalEntity`]
114/// entities. This warns if an entity has both components.
115pub fn debug_detect_updates_received_on_local_entities(
116 query: Query<Entity, (With<LocalEntity>, With<UpdatesReceived>)>,
117) {
118 for entity in &query {
119 warn!(
120 "Entity {:?} has both LocalEntity and UpdatesReceived",
121 entity
122 );
123 }
124}