Skip to content

Commit 2756eb4

Browse files
committed
optimizations at high entity counts
1 parent ca2fbe3 commit 2756eb4

File tree

10 files changed

+308
-141
lines changed

10 files changed

+308
-141
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ If you're working with low-level physics or packet related code, it's quite easy
1818

1919
The second major thing to watch out for is accidentally introducing performance regressions. Certain parts of Azalea are highly performance sensitive (notably, the pathfinder), so most changes in these areas should be benchmarked to avoid accidentally hurting performance.
2020

21+
You're encouraged to write relevant tests and benchmarks.
22+
2123
## Profiling
2224

2325
Please see [the chapter about profiling in the Rust performance book](https://nnethercote.github.io/perf-book/profiling.html).
@@ -29,12 +31,10 @@ cargo install flamegraph
2931
RUSTFLAGS="-C force-frame-pointers=yes" cargo r -r --example testbot
3032
# wait a few seconds so chunks being loaded doesn't affect the flamegraph, and
3133
# then run this in a separate window:
32-
flamegraph -p $(pidof testbot)
34+
flamegraph -p $(pidof testbot) --deterministic
3335
# wait about 15 seconds, then ctrl+c, and view the flamegraph.svg
3436
```
3537

3638
## AI Policy
3739

3840
Please avoid using generative AI to make contributions to Azalea. We do not enjoy working with code that wasn't written by people.
39-
40-

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
135135
# --- Profile Settings ---
136136

137137
[profile.release]
138-
debug = true
138+
debug = "line-tables-only"
139139

140140
# decoding packets takes forever if we don't do this
141141
[profile.dev.package.azalea-crypto]

azalea-client/src/plugins/packet/game/mod.rs

Lines changed: 84 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use azalea_core::{
99
};
1010
use azalea_entity::{
1111
ActiveEffects, Dead, EntityBundle, EntityKindComponent, HasClientLoaded, LoadedBy, LocalEntity,
12-
LookDirection, Physics, PlayerAbilities, Position, RelativeEntityUpdate,
12+
LookDirection, Physics, PlayerAbilities, Position,
1313
indexing::{EntityIdIndex, EntityUuidIndex},
1414
inventory::Inventory,
1515
metadata::{Health, apply_metadata},
@@ -36,7 +36,10 @@ use crate::{
3636
inventory::{ClientsideCloseContainerEvent, MenuOpenedEvent, SetContainerContentEvent},
3737
local_player::{Experience, Hunger, LocalGameMode, TabList, WorldHolder},
3838
movement::{KnockbackData, KnockbackEvent},
39-
packet::{as_system, declare_packet_handlers},
39+
packet::{
40+
as_system, declare_packet_handlers,
41+
relative_updates::{EntityUpdateQuery, RelativeEntityUpdate, should_apply_entity_update},
42+
},
4043
player::{GameProfileComponent, PlayerInfo},
4144
tick_counter::TicksConnected,
4245
};
@@ -716,36 +719,37 @@ impl GamePacketHandler<'_> {
716719
// vanilla servers use this packet for knockback, but note that the Explode
717720
// packet is also sometimes used by servers for knockback
718721

719-
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
720-
self.ecs,
721-
|(mut commands, query)| {
722-
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
723-
724-
let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
725-
// note that this log (and some other ones like the one in RemoveEntities)
726-
// sometimes happens when killing mobs. it seems to be a vanilla bug, which is
727-
// why it's a debug log instead of a warning
728-
debug!(
729-
"Got set entity motion packet for unknown entity id {}",
730-
p.id
731-
);
732-
return;
733-
};
734-
735-
// this is to make sure the same entity velocity update doesn't get sent
736-
// multiple times when in swarms
722+
as_system::<(
723+
Commands,
724+
Query<(&EntityIdIndex, &WorldHolder)>,
725+
EntityUpdateQuery,
726+
)>(self.ecs, |(mut commands, query, entity_update_query)| {
727+
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
737728

738-
let data = KnockbackData::Set(p.delta.to_vec3());
729+
let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
730+
// note that this log (and some other ones like the one in RemoveEntities)
731+
// sometimes happens when killing mobs. it seems to be a vanilla bug, which is
732+
// why it's a debug log instead of a warning
733+
debug!(
734+
"Got set entity motion packet for unknown entity id {}",
735+
p.id
736+
);
737+
return;
738+
};
739739

740-
commands.entity(entity).queue(RelativeEntityUpdate::new(
741-
world_holder.partial.clone(),
742-
move |entity_mut| {
743-
entity_mut
744-
.world_scope(|world| world.trigger(KnockbackEvent { entity, data }));
745-
},
746-
));
747-
},
748-
);
740+
let data = KnockbackData::Set(p.delta.to_vec3());
741+
742+
// this is to make sure the same entity velocity update doesn't get sent
743+
// multiple times when in swarms
744+
if should_apply_entity_update(
745+
&mut commands,
746+
&mut world_holder.partial.write(),
747+
entity,
748+
entity_update_query,
749+
) {
750+
commands.trigger(KnockbackEvent { entity, data });
751+
}
752+
});
749753
}
750754

751755
pub fn set_entity_link(&mut self, p: &ClientboundSetEntityLink) {
@@ -795,8 +799,8 @@ impl GamePacketHandler<'_> {
795799

796800
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
797801
self.ecs,
798-
|(mut commands, mut query)| {
799-
let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
802+
|(mut commands, query)| {
803+
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
800804

801805
let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
802806
warn!("Got teleport entity packet for unknown entity id {}", p.id);
@@ -840,8 +844,8 @@ impl GamePacketHandler<'_> {
840844
pub fn move_entity_pos(&mut self, p: &ClientboundMoveEntityPos) {
841845
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
842846
self.ecs,
843-
|(mut commands, mut query)| {
844-
let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
847+
|(mut commands, query)| {
848+
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
845849

846850
debug!("Got move entity pos packet {p:?}");
847851

@@ -879,8 +883,8 @@ impl GamePacketHandler<'_> {
879883
pub fn move_entity_pos_rot(&mut self, p: &ClientboundMoveEntityPosRot) {
880884
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
881885
self.ecs,
882-
|(mut commands, mut query)| {
883-
let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
886+
|(mut commands, query)| {
887+
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
884888

885889
debug!("Got move entity pos rot packet {p:?}");
886890

@@ -929,8 +933,8 @@ impl GamePacketHandler<'_> {
929933
pub fn move_entity_rot(&mut self, p: &ClientboundMoveEntityRot) {
930934
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
931935
self.ecs,
932-
|(mut commands, mut query)| {
933-
let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
936+
|(mut commands, query)| {
937+
let (entity_id_index, world_holder) = query.get(self.player).unwrap();
934938

935939
let entity = entity_id_index.get_by_minecraft_entity(p.entity_id);
936940
if let Some(entity) = entity {
@@ -1527,10 +1531,28 @@ impl GamePacketHandler<'_> {
15271531
}
15281532

15291533
pub fn entity_position_sync(&mut self, p: &ClientboundEntityPositionSync) {
1530-
as_system::<(Commands, Query<(&EntityIdIndex, &WorldHolder)>)>(
1534+
as_system::<(
1535+
Commands,
1536+
Query<(
1537+
&EntityIdIndex,
1538+
&WorldHolder,
1539+
Option<&LocalEntity>,
1540+
&mut Physics,
1541+
&mut Position,
1542+
&mut LookDirection,
1543+
)>,
1544+
EntityUpdateQuery,
1545+
)>(
15311546
self.ecs,
1532-
|(mut commands, mut query)| {
1533-
let (entity_id_index, world_holder) = query.get_mut(self.player).unwrap();
1547+
|(mut commands, mut query, entity_update_query)| {
1548+
let (
1549+
entity_id_index,
1550+
world_holder,
1551+
local_entity,
1552+
mut physics,
1553+
mut position,
1554+
mut look_direction,
1555+
) = query.get_mut(self.player).unwrap();
15341556

15351557
let Some(entity) = entity_id_index.get_by_minecraft_entity(p.id) else {
15361558
debug!("Got teleport entity packet for unknown entity id {}", p.id);
@@ -1541,28 +1563,32 @@ impl GamePacketHandler<'_> {
15411563
let new_on_ground = p.on_ground;
15421564
let new_look_direction = p.values.look_direction;
15431565

1544-
commands.entity(entity).queue(RelativeEntityUpdate::new(
1545-
world_holder.partial.clone(),
1546-
move |entity_mut| {
1547-
let is_local_entity = entity_mut.get::<LocalEntity>().is_some();
1548-
let mut physics = entity_mut.get_mut::<Physics>().unwrap();
1566+
if !should_apply_entity_update(
1567+
&mut commands,
1568+
&mut world_holder.partial.write(),
1569+
entity,
1570+
entity_update_query,
1571+
) {
1572+
return;
1573+
}
1574+
let is_local_entity = local_entity.is_some();
15491575

1550-
physics.vec_delta_codec.set_base(new_position);
1576+
physics.vec_delta_codec.set_base(new_position);
15511577

1552-
if is_local_entity {
1553-
debug!("Ignoring entity position sync packet for local player");
1554-
return;
1555-
}
1578+
if is_local_entity {
1579+
debug!("Ignoring entity position sync packet for local player");
1580+
return;
1581+
}
15561582

1557-
physics.set_on_ground(new_on_ground);
1583+
physics.set_on_ground(new_on_ground);
15581584

1559-
let mut position = entity_mut.get_mut::<Position>().unwrap();
1560-
**position = new_position;
1585+
if **position != new_position {
1586+
**position = new_position;
1587+
}
15611588

1562-
let mut look_direction = entity_mut.get_mut::<LookDirection>().unwrap();
1563-
*look_direction = new_look_direction;
1564-
},
1565-
));
1589+
if *look_direction != new_look_direction {
1590+
*look_direction = new_look_direction;
1591+
}
15661592
},
15671593
);
15681594
}

azalea-client/src/plugins/packet/mod.rs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::chat::ChatReceivedEvent;
1111
pub mod config;
1212
pub mod game;
1313
pub mod login;
14+
pub mod relative_updates;
1415

1516
pub struct PacketPlugin;
1617

@@ -30,23 +31,27 @@ pub fn death_event_on_0_health(
3031

3132
impl Plugin for PacketPlugin {
3233
fn build(&self, app: &mut App) {
33-
app.add_observer(game::handle_outgoing_packets_observer)
34-
.add_observer(config::handle_outgoing_packets_observer)
35-
.add_observer(login::handle_outgoing_packets_observer)
36-
.add_systems(Update, death_event_on_0_health)
37-
.add_message::<game::ReceiveGamePacketEvent>()
38-
.add_message::<config::ReceiveConfigPacketEvent>()
39-
.add_message::<login::ReceiveLoginPacketEvent>()
40-
//
41-
.add_message::<game::AddPlayerEvent>()
42-
.add_message::<game::RemovePlayerEvent>()
43-
.add_message::<game::UpdatePlayerEvent>()
44-
.add_message::<ChatReceivedEvent>()
45-
.add_message::<game::DeathEvent>()
46-
.add_message::<game::KeepAliveEvent>()
47-
.add_message::<game::ResourcePackEvent>()
48-
.add_message::<game::WorldLoadedEvent>()
49-
.add_message::<login::ReceiveCustomQueryEvent>();
34+
app.add_systems(
35+
Update,
36+
relative_updates::debug_detect_updates_received_on_local_entities,
37+
)
38+
.add_observer(game::handle_outgoing_packets_observer)
39+
.add_observer(config::handle_outgoing_packets_observer)
40+
.add_observer(login::handle_outgoing_packets_observer)
41+
.add_systems(Update, death_event_on_0_health)
42+
.add_message::<game::ReceiveGamePacketEvent>()
43+
.add_message::<config::ReceiveConfigPacketEvent>()
44+
.add_message::<login::ReceiveLoginPacketEvent>()
45+
//
46+
.add_message::<game::AddPlayerEvent>()
47+
.add_message::<game::RemovePlayerEvent>()
48+
.add_message::<game::UpdatePlayerEvent>()
49+
.add_message::<ChatReceivedEvent>()
50+
.add_message::<game::DeathEvent>()
51+
.add_message::<game::KeepAliveEvent>()
52+
.add_message::<game::ResourcePackEvent>()
53+
.add_message::<game::WorldLoadedEvent>()
54+
.add_message::<login::ReceiveCustomQueryEvent>();
5055
}
5156
}
5257

@@ -70,12 +75,21 @@ macro_rules! __declare_packet_handlers {
7075

7176
pub(crate) use __declare_packet_handlers as declare_packet_handlers;
7277

78+
#[derive(Resource)]
79+
struct CachedSystemState<T: SystemParam + 'static>(SystemState<T>);
80+
7381
pub(crate) fn as_system<T>(ecs: &mut World, f: impl FnOnce(T::Item<'_, '_>))
7482
where
7583
T: SystemParam + 'static,
7684
{
77-
let mut system_state = SystemState::<T>::new(ecs);
85+
// creating a new SystemState is expensive, so we save them as a Resource in the
86+
// ecs
87+
let mut system_state = match ecs.remove_resource::<CachedSystemState<T>>() {
88+
Some(s) => s.0,
89+
None => SystemState::<T>::new(ecs),
90+
};
7891
let values = system_state.get_mut(ecs);
7992
f(values);
8093
system_state.apply(ecs);
94+
ecs.insert_resource(CachedSystemState(system_state));
8195
}

0 commit comments

Comments
 (0)