From 000abfa13665abccf543b875d10c8c2a48dd75be Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 18 Nov 2023 14:54:01 -0600 Subject: [PATCH] make loading chunks its own bevy system --- .../src/{chunk_batching.rs => chunks.rs} | 99 ++++++++++++++++--- azalea-client/src/client.rs | 4 +- azalea-client/src/lib.rs | 2 +- .../src/packet_handling/configuration.rs | 2 +- azalea-client/src/packet_handling/game.rs | 69 +++---------- azalea/src/accept_resource_packs.rs | 2 +- 6 files changed, 103 insertions(+), 75 deletions(-) rename azalea-client/src/{chunk_batching.rs => chunks.rs} (64%) diff --git a/azalea-client/src/chunk_batching.rs b/azalea-client/src/chunks.rs similarity index 64% rename from azalea-client/src/chunk_batching.rs rename to azalea-client/src/chunks.rs index eda16442f..80c350c88 100644 --- a/azalea-client/src/chunk_batching.rs +++ b/azalea-client/src/chunks.rs @@ -2,26 +2,37 @@ //! for making the server spread out how often it sends us chunk packets //! depending on our receiving speed. -use std::time::{Duration, Instant}; +use std::{ + io::Cursor, + time::{Duration, Instant}, +}; -use azalea_protocol::packets::game::serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket; +use azalea_core::position::ChunkPos; +use azalea_nbt::NbtCompound; +use azalea_protocol::packets::game::{ + clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket, + serverbound_chunk_batch_received_packet::ServerboundChunkBatchReceivedPacket, +}; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; +use tracing::{error, trace}; use crate::{ interact::handle_block_interact_event, inventory::InventorySet, local_player::{handle_send_packet_event, SendPacketEvent}, respawn::perform_respawn, + InstanceHolder, }; -pub struct ChunkBatchingPlugin; -impl Plugin for ChunkBatchingPlugin { +pub struct ChunkPlugin; +impl Plugin for ChunkPlugin { fn build(&self, app: &mut App) { app.add_systems( Update, ( handle_chunk_batch_start_event, + handle_receive_chunk_events, handle_chunk_batch_finished_event, ) .chain() @@ -30,11 +41,18 @@ impl Plugin for ChunkBatchingPlugin { .before(handle_block_interact_event) .before(perform_respawn), ) + .add_event::() .add_event::() .add_event::(); } } +#[derive(Event)] +pub struct ReceiveChunkEvent { + pub entity: Entity, + pub packet: ClientboundLevelChunkWithLightPacket, +} + #[derive(Component, Clone, Debug)] pub struct ChunkBatchInfo { pub start_time: Instant, @@ -42,6 +60,69 @@ pub struct ChunkBatchInfo { pub old_samples_weight: u32, } +#[derive(Event)] +pub struct ChunkBatchStartEvent { + pub entity: Entity, +} +#[derive(Event)] +pub struct ChunkBatchFinishedEvent { + pub entity: Entity, + pub batch_size: u32, +} + +fn handle_receive_chunk_events( + mut events: EventReader, + mut query: Query<&mut InstanceHolder>, +) { + for event in events.read() { + let pos = ChunkPos::new(event.packet.x, event.packet.z); + + let local_player = query.get_mut(event.entity).unwrap(); + + // OPTIMIZATION: if we already know about the chunk from the + // shared world (and not ourselves), then we don't need to + // parse it again. This is only used when we have a shared + // world, since we check that the chunk isn't currently owned + // by this client. + let shared_chunk = local_player.instance.read().chunks.get(&pos); + let this_client_has_chunk = local_player + .partial_instance + .read() + .chunks + .limited_get(&pos) + .is_some(); + + let mut world = local_player.instance.write(); + let mut partial_world = local_player.partial_instance.write(); + + if !this_client_has_chunk { + if let Some(shared_chunk) = shared_chunk { + trace!("Skipping parsing chunk {pos:?} because we already know about it"); + partial_world.chunks.set_with_shared_reference( + &pos, + Some(shared_chunk.clone()), + &mut world.chunks, + ); + continue; + } + } + + let heightmaps = event.packet.chunk_data.heightmaps.as_compound(); + // necessary to make the unwrap_or work + let empty_nbt_compound = NbtCompound::default(); + let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); + + if let Err(e) = partial_world.chunks.replace_with_packet_data( + &pos, + &mut Cursor::new(&event.packet.chunk_data.data), + heightmaps, + &mut world.chunks, + ) { + error!("Couldn't set chunk data: {e}"); + } + } +} + impl ChunkBatchInfo { pub fn batch_finished(&mut self, batch_size: u32) { if batch_size == 0 { @@ -65,16 +146,6 @@ impl ChunkBatchInfo { } } -#[derive(Event)] -pub struct ChunkBatchStartEvent { - pub entity: Entity, -} -#[derive(Event)] -pub struct ChunkBatchFinishedEvent { - pub entity: Entity, - pub batch_size: u32, -} - pub fn handle_chunk_batch_start_event( mut query: Query<&mut ChunkBatchInfo>, mut events: EventReader, diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 96e4eb1c2..4f1c4b9ea 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -1,7 +1,7 @@ use crate::{ attack::{self, AttackPlugin}, chat::ChatPlugin, - chunk_batching::{ChunkBatchInfo, ChunkBatchingPlugin}, + chunks::{ChunkBatchInfo, ChunkPlugin}, disconnect::{DisconnectEvent, DisconnectPlugin}, events::{Event, EventPlugin, LocalPlayerEvents}, interact::{CurrentSequenceNumber, InteractPlugin}, @@ -782,7 +782,7 @@ impl PluginGroup for DefaultPlugins { .add(RespawnPlugin) .add(MinePlugin) .add(AttackPlugin) - .add(ChunkBatchingPlugin) + .add(ChunkPlugin) .add(TickBroadcastPlugin); #[cfg(feature = "log")] { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index a7231ffc7..bc5616e88 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -12,7 +12,7 @@ mod account; pub mod attack; pub mod chat; -pub mod chunk_batching; +pub mod chunks; mod client; pub mod disconnect; mod entity_query; diff --git a/azalea-client/src/packet_handling/configuration.rs b/azalea-client/src/packet_handling/configuration.rs index b82ed76ff..f35e25b2d 100644 --- a/azalea-client/src/packet_handling/configuration.rs +++ b/azalea-client/src/packet_handling/configuration.rs @@ -140,7 +140,7 @@ pub fn process_packet_events(ecs: &mut World) { abilities: crate::local_player::PlayerAbilities::default(), permission_level: crate::local_player::PermissionLevel::default(), hunger: Hunger::default(), - chunk_batch_info: crate::chunk_batching::ChunkBatchInfo::default(), + chunk_batch_info: crate::chunks::ChunkBatchInfo::default(), entity_id_index: EntityIdIndex::default(), diff --git a/azalea-client/src/packet_handling/game.rs b/azalea-client/src/packet_handling/game.rs index 3d61d7900..717261424 100644 --- a/azalea-client/src/packet_handling/game.rs +++ b/azalea-client/src/packet_handling/game.rs @@ -16,7 +16,6 @@ use azalea_entity::{ Dead, EntityBundle, EntityKind, LastSentPosition, LoadedBy, LocalEntity, LookDirection, Physics, PlayerBundle, Position, RelativeEntityUpdate, }; -use azalea_nbt::NbtCompound; use azalea_protocol::{ packets::game::{ clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, @@ -34,7 +33,7 @@ use tracing::{debug, error, trace, warn}; use crate::{ chat::{ChatPacket, ChatReceivedEvent}, - chunk_batching, + chunks, disconnect::DisconnectEvent, inventory::{ ClientSideCloseContainerEvent, InventoryComponent, MenuOpenedEvent, @@ -339,24 +338,22 @@ pub fn process_packet_events(ecs: &mut World) { ClientboundGamePacket::ChunkBatchStart(_p) => { // the packet is empty, just a marker to tell us when the batch starts and ends debug!("Got chunk batch start"); - let mut system_state: SystemState< - EventWriter, - > = SystemState::new(ecs); + let mut system_state: SystemState> = + SystemState::new(ecs); let mut chunk_batch_start_events = system_state.get_mut(ecs); - chunk_batch_start_events.send(chunk_batching::ChunkBatchStartEvent { + chunk_batch_start_events.send(chunks::ChunkBatchStartEvent { entity: player_entity, }); } ClientboundGamePacket::ChunkBatchFinished(p) => { debug!("Got chunk batch finished {p:?}"); - let mut system_state: SystemState< - EventWriter, - > = SystemState::new(ecs); + let mut system_state: SystemState> = + SystemState::new(ecs); let mut chunk_batch_start_events = system_state.get_mut(ecs); - chunk_batch_start_events.send(chunk_batching::ChunkBatchFinishedEvent { + chunk_batch_start_events.send(chunks::ChunkBatchFinishedEvent { entity: player_entity, batch_size: p.batch_size, }); @@ -597,54 +594,14 @@ pub fn process_packet_events(ecs: &mut World) { } ClientboundGamePacket::LevelChunkWithLight(p) => { debug!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - let mut system_state: SystemState> = + let mut system_state: SystemState> = SystemState::new(ecs); - let mut query = system_state.get_mut(ecs); - let local_player = query.get_mut(player_entity).unwrap(); - - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_chunk = local_player.instance.read().chunks.get(&pos); - let this_client_has_chunk = local_player - .partial_instance - .read() - .chunks - .limited_get(&pos) - .is_some(); - - let mut world = local_player.instance.write(); - let mut partial_world = local_player.partial_instance.write(); - - if !this_client_has_chunk { - if let Some(shared_chunk) = shared_chunk { - trace!("Skipping parsing chunk {pos:?} because we already know about it"); - partial_world.chunks.set_with_shared_reference( - &pos, - Some(shared_chunk.clone()), - &mut world.chunks, - ); - continue; - } - } - - let heightmaps = p.chunk_data.heightmaps.as_compound(); - // necessary to make the unwrap_or work - let empty_nbt_compound = NbtCompound::default(); - let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound); - - if let Err(e) = partial_world.chunks.replace_with_packet_data( - &pos, - &mut Cursor::new(&p.chunk_data.data), - heightmaps, - &mut world.chunks, - ) { - error!("Couldn't set chunk data: {e}"); - } + let mut receive_chunk_events = system_state.get_mut(ecs); + receive_chunk_events.send(chunks::ReceiveChunkEvent { + entity: player_entity, + packet: p.clone(), + }); } ClientboundGamePacket::AddEntity(p) => { debug!("Got add entity packet {p:?}"); diff --git a/azalea/src/accept_resource_packs.rs b/azalea/src/accept_resource_packs.rs index 281af420f..6fdb40dbd 100644 --- a/azalea/src/accept_resource_packs.rs +++ b/azalea/src/accept_resource_packs.rs @@ -1,5 +1,5 @@ use crate::app::{App, Plugin}; -use azalea_client::chunk_batching::handle_chunk_batch_finished_event; +use azalea_client::chunks::handle_chunk_batch_finished_event; use azalea_client::inventory::InventorySet; use azalea_client::packet_handling::{death_event_on_0_health, game::ResourcePackEvent}; use azalea_client::respawn::perform_respawn;