diff --git a/.cargo/config.toml b/.cargo/config.toml index 4a059be..81f4d69 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,4 +4,6 @@ BEVY_ASSET_ROOT = "." [alias] rc = 'run --bin client' rs = 'run --bin server' +tc = 'run --bin client --features bevy/trace_tracy' +ts = 'run --bin server --features bevy/trace_tracy' t = 'test' diff --git a/src/client/player/systems/controller.rs b/src/client/player/systems/controller.rs index 58c7a63..35ea22a 100644 --- a/src/client/player/systems/controller.rs +++ b/src/client/player/systems/controller.rs @@ -4,7 +4,7 @@ use crate::prelude::*; const SPAWN_POINT: Vec3 = Vec3::new(0.0, 1.0, 0.0); #[cfg(all(not(feature = "skip_terrain"), not(feature = "lock_player")))] -const SPAWN_POINT: Vec3 = Vec3::new(0.0, 64.0, 0.0); +const SPAWN_POINT: Vec3 = Vec3::new(0.0, 43.0, 0.0); // TODO: determine terrain height at 0,0 #[cfg(all(not(feature = "skip_terrain"), feature = "lock_player"))] const SPAWN_POINT: Vec3 = Vec3::new(128.0, 96.0, -128.0); diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 30074c0..bed45ff 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -53,11 +53,17 @@ pub fn generate_world_system( info!("Sending chunk requests for chunks"); - let chunks = chunk_manager.instantiate_new_chunks(IVec3::ZERO, render_distance); + let origin = IVec3::ZERO; + let chunks = chunk_manager.instantiate_new_chunks(origin, render_distance); - let positions: Vec = chunks.into_iter().map(|chunk| chunk.position).collect(); + let mut positions: Vec = chunks.into_iter().map(|chunk| chunk.position).collect(); + positions.sort_by(|a, b| { + (a - origin) + .length_squared() + .cmp(&(b - origin).length_squared()) + }); - let batched_positions = positions.chunks(16); + let batched_positions = positions.chunks(32); assert!(batched_positions.len() > 0, "Batched positions is empty"); batched_positions.enumerate().for_each(|(index, batch)| { diff --git a/src/server/networking/systems.rs b/src/server/networking/systems.rs index 743ce46..367d7a2 100644 --- a/src/server/networking/systems.rs +++ b/src/server/networking/systems.rs @@ -4,11 +4,10 @@ pub fn receive_message_system( mut server: ResMut, mut player_states: ResMut, mut past_block_updates: ResMut, - chunk_manager: ResMut, + mut request_queue: ResMut, #[cfg(feature = "chat")] mut chat_message_events: MessageWriter< chat_events::PlayerChatMessageSendEvent, >, - generator: Res, ) { for client_id in server.clients_id() { while let Some(message) = server.receive_message(client_id, DefaultChannel::ReliableOrdered) @@ -64,30 +63,7 @@ pub fn receive_message_system( positions, client_id ); - let chunks: Vec = positions - .into_par_iter() - .map(|position| { - let chunk = chunk_manager.get_chunk(position); - - match chunk { - Some(chunk) => *chunk, - None => { - let mut chunk = Chunk::new(position); - generator.generate_chunk(&mut chunk); - chunk - } - } - }) - .collect(); - - let message = - bincode::serialize(&NetworkingMessage::ChunkBatchResponse(chunks)); - - server.send_message( - client_id, - DefaultChannel::ReliableUnordered, - message.unwrap(), - ); + request_queue.enqueue_bulk(client_id, &mut positions.into()); } _ => { warn!("Received unknown message type. (ReliableUnordered)"); @@ -102,6 +78,7 @@ pub fn handle_events_system( mut server_events: MessageReader, mut player_states: ResMut, past_block_updates: Res, + mut request_queue: ResMut, #[cfg(feature = "chat")] mut chat_message_events: MessageWriter< chat_events::PlayerChatMessageSendEvent, >, @@ -153,6 +130,8 @@ pub fn handle_events_system( println!("Client {client_id} disconnected: {reason}"); player_states.players.remove(client_id); + request_queue.remove(*client_id); + #[cfg(feature = "chat")] chat_message_events.write(chat_events::PlayerChatMessageSendEvent { client_id: SERVER_MESSAGE_ID, diff --git a/src/server/terrain/mod.rs b/src/server/terrain/mod.rs index f28b5a0..bd174a7 100644 --- a/src/server/terrain/mod.rs +++ b/src/server/terrain/mod.rs @@ -13,7 +13,9 @@ impl Plugin for TerrainPlugin { app.add_message::(); app.insert_resource(resources::PastBlockUpdates::new()); app.add_systems(Startup, terrain_systems::setup_world_system); + app.add_systems(Update, terrain_systems::process_user_chunk_requests_system); app.insert_resource(resources::Generator::default()); + app.insert_resource(resources::ClientChunkRequests::default()); #[cfg(feature = "generator_visualizer")] { diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index 199798d..c6aad4b 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -1,7 +1,34 @@ +use std::collections::VecDeque; + use crate::prelude::*; use terrain_events::BlockUpdateEvent; +#[derive(Resource, Default)] +pub struct ClientChunkRequests { + queues: HashMap>, +} + +impl ClientChunkRequests { + pub fn enqueue_bulk(&mut self, client_id: ClientId, chunk_positions: &mut VecDeque) { + self.queues + .entry(client_id) + .or_default() + .append(chunk_positions); + } + + pub fn remove(&mut self, client_id: ClientId) { + self.queues.remove(&client_id); + } + + pub fn retain(&mut self, f: F) + where + F: FnMut(&ClientId, &mut VecDeque) -> bool, + { + self.queues.retain(f) + } +} + #[derive(Resource)] pub struct PastBlockUpdates { pub updates: Vec, diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index 217b9f4..fed5a7e 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -1,3 +1,5 @@ +use std::cmp::min; + use crate::prelude::*; pub fn setup_world_system( @@ -18,6 +20,50 @@ pub fn setup_world_system( chunk_manager.insert_chunks(chunks); } +pub fn process_user_chunk_requests_system( + mut requests: ResMut, + chunk_manager: Res, + mut server: ResMut, + generator: Res, +) { + const MAX_REQUESTS_PER_CYCLE_PER_PLAYER: usize = 5; + + requests.retain(|client_id, positions| { + if positions.is_empty() { + return false; + } + + let take_count = min(MAX_REQUESTS_PER_CYCLE_PER_PLAYER, positions.len()); + let positions_to_process: Vec = positions.drain(0..take_count).collect(); + + let chunks = positions_to_process + .into_par_iter() + .map(|position| { + let chunk = chunk_manager.get_chunk(position); + + match chunk { + Some(chunk) => *chunk, + None => { + let mut chunk = Chunk::new(position); + generator.generate_chunk(&mut chunk); + chunk + } + } + }) + .collect(); + + let message = bincode::serialize(&NetworkingMessage::ChunkBatchResponse(chunks)); + + server.send_message( + *client_id, + DefaultChannel::ReliableUnordered, + message.unwrap(), + ); + + !positions.is_empty() + }); +} + #[cfg(feature = "generator_visualizer")] pub use visualizer::*;