diff options
| author | mat <git@matdoes.dev> | 2026-02-22 11:35:41 +0700 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2026-02-22 11:35:41 +0700 |
| commit | 2756eb419af2210eed3d0574e20a620918e4e577 (patch) | |
| tree | 0d209e70f65db16b7b982c3e941355589b7d5181 /azalea-entity | |
| parent | ca2fbe329b0879496cf12de4e85d81ca3aa3c351 (diff) | |
| download | azalea-drasl-2756eb419af2210eed3d0574e20a620918e4e577.tar.xz | |
optimizations at high entity counts
Diffstat (limited to 'azalea-entity')
| -rw-r--r-- | azalea-entity/src/lib.rs | 4 | ||||
| -rw-r--r-- | azalea-entity/src/plugin/components.rs | 4 | ||||
| -rw-r--r-- | azalea-entity/src/plugin/mod.rs | 52 | ||||
| -rw-r--r-- | azalea-entity/src/plugin/relative_updates.rs | 115 |
4 files changed, 34 insertions, 141 deletions
diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index fa4feffb..aacc3f54 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -424,6 +424,10 @@ pub struct PlayerAbilities { /// The type of fluid that is at an entity's eye position, while also accounting /// for fluid height. +/// +/// This is only updated for [`AbstractLiving`] entities. +/// +/// [`AbstractLiving`]: metadata::AbstractLiving #[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::component::Component))] #[derive(Clone, Copy, Debug, Deref, DerefMut, PartialEq)] pub struct FluidOnEyes(FluidKind); diff --git a/azalea-entity/src/plugin/components.rs b/azalea-entity/src/plugin/components.rs index 4698a808..eaeeb0d0 100644 --- a/azalea-entity/src/plugin/components.rs +++ b/azalea-entity/src/plugin/components.rs @@ -94,8 +94,8 @@ pub struct OnClimbable(bool); /// A component that indicates whether the player is currently sneaking. /// -/// If the entity isn't a local player, then this is just a shortcut for -/// checking if the [`Pose`] is `Crouching`. +/// If the entity is a player but isn't a local player, then this is just a +/// shortcut for checking if the [`Pose`] is `Crouching`. /// /// If you need to modify this value, use /// `azalea_client::PhysicsState::trying_to_crouch` or `Client::set_crouching` diff --git a/azalea-entity/src/plugin/mod.rs b/azalea-entity/src/plugin/mod.rs index e40c24f2..b86c6b7d 100644 --- a/azalea-entity/src/plugin/mod.rs +++ b/azalea-entity/src/plugin/mod.rs @@ -1,6 +1,5 @@ mod components; pub mod indexing; -mod relative_updates; use std::collections::HashSet; @@ -17,13 +16,12 @@ use bevy_ecs::prelude::*; pub use components::*; use derive_more::{Deref, DerefMut}; use indexing::EntityUuidIndex; -pub use relative_updates::RelativeEntityUpdate; use tracing::debug; use crate::{ FluidOnEyes, LookDirection, Physics, Pose, Position, dimensions::{EntityDimensions, calculate_dimensions}, - metadata::Health, + metadata::{self, Health, Player}, }; /// A Bevy [`SystemSet`] for various types of entity updates. @@ -57,7 +55,6 @@ impl Plugin for EntityPlugin { .chain() .in_set(EntityUpdateSystems::Index), ( - relative_updates::debug_detect_updates_received_on_local_entities, debug_new_entity, add_dead, clamp_look_direction, @@ -97,27 +94,32 @@ pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed< } pub fn update_fluid_on_eyes( - mut query: Query<(&mut FluidOnEyes, &Position, &EntityDimensions, &WorldName)>, + mut query: Query< + (&mut FluidOnEyes, &Position, &EntityDimensions, &WorldName), + With<metadata::AbstractLiving>, + >, worlds: Res<Worlds>, ) { - for (mut fluid_on_eyes, position, dimensions, world_name) in query.iter_mut() { - let Some(world) = worlds.get(world_name) else { - continue; - }; - - let adjusted_eye_y = position.y + (dimensions.eye_height as f64) - 0.1111111119389534; - let eye_block_pos = BlockPos::from(position.with_y(adjusted_eye_y)); - let fluid_at_eye = world - .read() - .get_fluid_state(eye_block_pos) - .unwrap_or_default(); - let fluid_cutoff_y = (eye_block_pos.y as f32 + fluid_at_eye.height()) as f64; - if fluid_cutoff_y > adjusted_eye_y { - **fluid_on_eyes = fluid_at_eye.kind; - } else { - **fluid_on_eyes = FluidKind::Empty; - } - } + query + .par_iter_mut() + .for_each(|(mut fluid_on_eyes, position, dimensions, world_name)| { + let Some(world) = worlds.get(world_name) else { + return; + }; + + let adjusted_eye_y = position.y + (dimensions.eye_height as f64) - 0.1111111119389534; + let eye_block_pos = BlockPos::from(position.with_y(adjusted_eye_y)); + let fluid_at_eye = world + .read() + .get_fluid_state(eye_block_pos) + .unwrap_or_default(); + let fluid_cutoff_y = (eye_block_pos.y as f32 + fluid_at_eye.height()) as f64; + if fluid_cutoff_y > adjusted_eye_y { + **fluid_on_eyes = fluid_at_eye.kind; + } else { + **fluid_on_eyes = FluidKind::Empty; + } + }); } pub fn update_on_climbable( @@ -229,7 +231,9 @@ pub fn update_dimensions( } } -pub fn update_crouching(query: Query<(&mut Crouching, &Pose), Without<LocalEntity>>) { +pub fn update_crouching( + query: Query<(&mut Crouching, &Pose), (Without<LocalEntity>, With<Player>)>, +) { for (mut crouching, pose) in query { let new_crouching = *pose == Pose::Crouching; // avoid triggering change detection diff --git a/azalea-entity/src/plugin/relative_updates.rs b/azalea-entity/src/plugin/relative_updates.rs deleted file mode 100644 index 53eb4c95..00000000 --- a/azalea-entity/src/plugin/relative_updates.rs +++ /dev/null @@ -1,115 +0,0 @@ -// How entity updates are processed (to avoid issues with shared worlds) -// - each bot contains a map of { entity id: updates received } -// - the shared world also contains a canonical "true" updates received for each -// entity -// - when a client loads an entity, its "updates received" is set to the same as -// the global "updates received" -// - when the shared world sees an entity for the first time, the "updates -// received" is set to 1. -// - clients can force the shared "updates received" to 0 to make it so certain -// entities (i.e. other bots in our swarm) don't get confused and updated by -// other bots -// - when a client gets an update to an entity, we check if our "updates -// received" is the same as the shared world's "updates received": if it is, -// then process the update and increment the client's and shared world's -// "updates received" if not, then we simply increment our local "updates -// received" and do nothing else - -use std::sync::Arc; - -use azalea_core::entity_id::MinecraftEntityId; -use azalea_world::PartialWorld; -use bevy_ecs::prelude::*; -use derive_more::{Deref, DerefMut}; -use parking_lot::RwLock; -use tracing::warn; - -use crate::LocalEntity; - -/// An [`EntityCommand`] that applies a "relative update" to an entity, which -/// means this update won't be run multiple times by different clients in the -/// same world. -/// -/// This is used to avoid a bug where when there's multiple clients in the same -/// world and an entity sends a relative move packet to all clients, its -/// position gets desynced since the relative move is applied multiple times. -/// -/// Don't use this unless you actually got an entity update packet that all -/// other clients within render distance will get too. You usually don't need -/// this when the change isn't relative either. -pub struct RelativeEntityUpdate { - pub partial_world: Arc<RwLock<PartialWorld>>, - // a function that takes the entity and updates it - pub update: Box<dyn FnOnce(&mut EntityWorldMut) + Send + Sync>, -} -impl RelativeEntityUpdate { - pub fn new( - partial_world: Arc<RwLock<PartialWorld>>, - update: impl FnOnce(&mut EntityWorldMut) + Send + Sync + 'static, - ) -> Self { - Self { - partial_world, - update: Box::new(update), - } - } -} - -/// A component that counts the number of times this entity has been modified. -/// -/// This is used for making sure two clients don't do the same relative update -/// on an entity. -/// -/// If an entity is local (i.e. it's a client/LocalEntity), this component -/// should NOT be present in the entity. -#[derive(Component, Debug, Deref, DerefMut)] -pub struct UpdatesReceived(u32); - -impl EntityCommand for RelativeEntityUpdate { - fn apply(self, mut entity: EntityWorldMut) { - let partial_entity_infos = &mut self.partial_world.write().entity_infos; - - if Some(entity.id()) == partial_entity_infos.owner_entity { - // if the entity owns this partial world, it's always allowed to update itself - (self.update)(&mut entity); - return; - }; - - let entity_id = *entity.get::<MinecraftEntityId>().unwrap(); - if entity.contains::<LocalEntity>() { - // a client tried to update another client, which isn't allowed - return; - } - - let this_client_updates_received = partial_entity_infos - .updates_received - .get(&entity_id) - .copied(); - - let can_update = if let Some(updates_received) = entity.get::<UpdatesReceived>() { - this_client_updates_received.unwrap_or(1) == **updates_received - } else { - // no UpdatesReceived means the entity was just spawned - true - }; - if can_update { - let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; - partial_entity_infos - .updates_received - .insert(entity_id, new_updates_received); - - entity.insert(UpdatesReceived(new_updates_received)); - - (self.update)(&mut entity); - } - } -} - -/// A system that logs a warning if an entity has both [`UpdatesReceived`] -/// and [`LocalEntity`]. -pub fn debug_detect_updates_received_on_local_entities( - query: Query<Entity, (With<LocalEntity>, With<UpdatesReceived>)>, -) { - for entity in &query { - warn!("Entity {entity:?} has both LocalEntity and UpdatesReceived"); - } -} |
