diff --git a/CHANGELOG.md b/CHANGELOG.md index 4298f11e..6a8892d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Add `bevy-inspector-egui` - Update state management to use bevy `State` instead of a custom `StateMachine` - Add `renet_visualizer` to visualize network traffic on the server +- Refactor shared code into `lib` namespace +- Drop `lib::` prefix +- Increase world size +- Fix issue where chunks were only serialized on the client ## 0.1.1 diff --git a/README.md b/README.md index e86ce765..45dc6877 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,25 @@ cargo run --bin client ``` -In order to create a more optimized build, you can pass the `release` flag: +### More optimal setup + +Release Builds (for better performance): ```bash cargo run --bin server --release cargo run --bin client --release +``` + +Dynamic Linking (to reduce compile times): +```bash +cargo run --bin server --features dynamic_linking +cargo run --bin client --features dynamic_linking +``` +Automatic Reloading (with [cargo watch](https://docs.rs/crate/cargo-watch)): +```bash +cargo watch -x 'run --bin server' +cargo watch -x 'run --bin client' ``` ### Installation on NixOS diff --git a/src/client/chat/events.rs b/src/client/chat/events.rs index 12799a5a..cee9254a 100644 --- a/src/client/chat/events.rs +++ b/src/client/chat/events.rs @@ -1,10 +1,10 @@ use crate::prelude::*; #[derive(Event)] -pub struct ChatSyncEvent(pub Vec); +pub struct ChatSyncEvent(pub Vec); #[derive(Event)] -pub struct SingleChatSendEvent(pub lib::ChatMessage); +pub struct SingleChatSendEvent(pub ChatMessage); #[derive(Event)] pub struct ChatMessageSendEvent(pub String); diff --git a/src/client/chat/resources.rs b/src/client/chat/resources.rs index 40fbbcee..fa38f82f 100644 --- a/src/client/chat/resources.rs +++ b/src/client/chat/resources.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Resource, Default, Debug)] pub struct ChatHistory { - pub messages: Vec, + pub messages: Vec, } #[derive(Resource, Default)] diff --git a/src/client/chat/systems.rs b/src/client/chat/systems.rs index 3e71085d..8ccf3758 100644 --- a/src/client/chat/systems.rs +++ b/src/client/chat/systems.rs @@ -247,9 +247,9 @@ pub fn focus_chat_system( #[cfg(test)] mod tests { use super::*; + use crate::ChatMessage; use bevy::ecs::event::Events; use chat_events::{ChatClearEvent, SingleChatSendEvent}; - use rsmc::ChatMessage; fn get_chat_messages(app: &mut App) -> Vec { let mut messages = app diff --git a/src/client/collider/systems.rs b/src/client/collider/systems.rs index bb0fce8b..8b7c2d0c 100644 --- a/src/client/collider/systems.rs +++ b/src/client/collider/systems.rs @@ -8,7 +8,7 @@ pub fn setup_coliders_system(mut commands: Commands) { let collider_range = 0..COLLIDER_GRID_SIZE; commands.spawn(( - Collider::cuboid(32.0, 1.0, 32.0), + Collider::cuboid(256.0, 1.0, 256.0), Transform::from_xyz(0.0, 0.0, 0.0), )); @@ -39,7 +39,7 @@ pub fn setup_coliders_system(mut commands: Commands) { pub fn handle_collider_update_events_system( mut collider_grid_events: EventReader, mut query: Query<(&mut Transform, &collider_components::BlockCollider)>, - mut chunk_manager: ResMut, + mut chunk_manager: ResMut, ) { for event in collider_grid_events.read() { let event_position = Vec3::new( @@ -102,7 +102,7 @@ mod tests { app.add_event::(); app.add_systems(Update, handle_collider_update_events_system); - app.insert_resource(terrain_resources::ChunkManager::new()); + app.insert_resource(ChunkManager::new()); app.world_mut().spawn(( Transform { @@ -123,17 +123,14 @@ mod tests { )); let block = BlockId::Dirt; - let mut resource = app - .world_mut() - .get_resource_mut::() - .unwrap(); - let chunks = terrain_resources::ChunkManager::instantiate_chunks( + let mut resource = app.world_mut().get_resource_mut::().unwrap(); + let chunks = ChunkManager::instantiate_chunks( Vec3 { x: 0.0, y: 0.0, z: 0.0, }, - 1, + Vec3::new(1.0, 1.0, 1.0), ); resource.insert_chunks(chunks); resource.set_block( diff --git a/src/client/networking/mod.rs b/src/client/networking/mod.rs index 830ec903..8fe37d70 100644 --- a/src/client/networking/mod.rs +++ b/src/client/networking/mod.rs @@ -1,10 +1,10 @@ pub mod systems; +use crate::connection_config; use bevy_renet::{ netcode::{ClientAuthentication, NetcodeClientPlugin, NetcodeClientTransport}, RenetClientPlugin, }; -use renet::{ChannelConfig, SendType}; use crate::prelude::*; @@ -15,36 +15,7 @@ impl Plugin for NetworkingPlugin { fn build(&self, app: &mut App) { app.add_plugins((RenetClientPlugin, NetcodeClientPlugin)); - let channel_config_unreliable = ChannelConfig { - channel_id: 0, - max_memory_usage_bytes: 1000 * 1024 * 1024, - send_type: SendType::Unreliable, - }; - - let channel_config_reliable_ordered = ChannelConfig { - channel_id: 1, - max_memory_usage_bytes: 1000 * 1024 * 1024, - send_type: SendType::ReliableOrdered { - resend_time: Duration::from_millis(300), - }, - }; - - let channel_config_reliable_unordered = ChannelConfig { - channel_id: 2, - max_memory_usage_bytes: 1000 * 1024 * 1024, - send_type: SendType::ReliableUnordered { - resend_time: Duration::from_millis(300), - }, - }; - - let client = RenetClient::new(ConnectionConfig { - client_channels_config: Vec::from([ - channel_config_unreliable, - channel_config_reliable_ordered, - channel_config_reliable_unordered, - ]), - ..Default::default() - }); + let client = RenetClient::new(connection_config()); app.insert_resource(client); let client_id = rand::random::(); diff --git a/src/client/networking/systems.rs b/src/client/networking/systems.rs index a2f8762f..1b6cbaa4 100644 --- a/src/client/networking/systems.rs +++ b/src/client/networking/systems.rs @@ -7,7 +7,7 @@ pub fn receive_message_system( mut player_despawn_events: ResMut>, mut player_sync_events: ResMut>, mut block_update_events: ResMut>, - mut chunk_manager: ResMut, + mut chunk_manager: ResMut, mut chunk_mesh_events: ResMut>, mut chat_events: ResMut>, mut single_chat_events: ResMut>, @@ -16,18 +16,18 @@ pub fn receive_message_system( while let Some(message) = client.receive_message(DefaultChannel::ReliableOrdered) { match bincode::deserialize(&message) { Ok(message) => match message { - lib::NetworkingMessage::PlayerJoin(event) => { + NetworkingMessage::PlayerJoin(event) => { player_spawn_events.send(remote_player_events::RemotePlayerSpawnedEvent { client_id: event, position: Vec3::ZERO, }); } - lib::NetworkingMessage::PlayerLeave(event) => { + NetworkingMessage::PlayerLeave(event) => { player_despawn_events.send(remote_player_events::RemotePlayerDespawnedEvent { client_id: event, }); } - lib::NetworkingMessage::BlockUpdate { position, block } => { + NetworkingMessage::BlockUpdate { position, block } => { debug!("Client received block update message: {:?}", position); block_update_events.send(terrain_events::BlockUpdateEvent { position, @@ -35,11 +35,11 @@ pub fn receive_message_system( from_network: true, }); } - lib::NetworkingMessage::ChatMessageSync(messages) => { + NetworkingMessage::ChatMessageSync(messages) => { info!("Client received {} chat messages", messages.len()); chat_events.send(chat_events::ChatSyncEvent(messages)); } - lib::NetworkingMessage::SingleChatMessageSync(message) => { + NetworkingMessage::SingleChatMessageSync(message) => { info!("Client received chat message {}", message.message); single_chat_events.send(chat_events::SingleChatSendEvent(message)); } @@ -64,7 +64,7 @@ pub fn receive_message_system( if let Ok(message) = message { debug!("Received message: {:?}", message); match message { - lib::NetworkingMessage::ChunkBatchResponse(chunks) => { + NetworkingMessage::ChunkBatchResponse(chunks) => { info!("Client received chunk batch response message."); for chunk in chunks { info!( @@ -87,7 +87,7 @@ pub fn receive_message_system( } } } - lib::NetworkingMessage::PlayerSync(event) => { + NetworkingMessage::PlayerSync(event) => { player_sync_events .send(remote_player_events::RemotePlayerSyncEvent { players: event }); } diff --git a/src/client/player/systems/network.rs b/src/client/player/systems/network.rs index 81f9d398..b89a5bb6 100644 --- a/src/client/player/systems/network.rs +++ b/src/client/player/systems/network.rs @@ -12,13 +12,13 @@ pub fn broadcast_player_attributes_system( let (_, transform) = query.single(); let (_, _, camera_transform) = camera_query.single(); - let player_state = lib::PlayerState { + let player_state = PlayerState { position: transform.translation, rotation: camera_transform.rotation, }; client.send_message( DefaultChannel::ReliableUnordered, - bincode::serialize(&lib::NetworkingMessage::PlayerUpdate(player_state)).unwrap(), + bincode::serialize(&NetworkingMessage::PlayerUpdate(player_state)).unwrap(), ); } diff --git a/src/client/player/systems/terrain.rs b/src/client/player/systems/terrain.rs index ca2c91ac..398c87e5 100644 --- a/src/client/player/systems/terrain.rs +++ b/src/client/player/systems/terrain.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub fn handle_block_update_events( - mut chunk_manager: ResMut, + mut chunk_manager: ResMut, mut block_update_events: EventReader, mut chunk_mesh_update_events: EventWriter, mut player_collider_events: EventWriter, diff --git a/src/client/prelude.rs b/src/client/prelude.rs index 7680f8b5..0a98c486 100644 --- a/src/client/prelude.rs +++ b/src/client/prelude.rs @@ -31,9 +31,11 @@ pub use renet::{ClientId, ConnectionConfig, DefaultChannel, RenetClient}; // other crates pub use iyes_perf_ui::prelude::*; +pub use rayon::iter::IntoParallelIterator; +pub use rayon::iter::IntoParallelRefMutIterator; +pub use rayon::iter::ParallelIterator; pub use serde::*; -pub use self::lib::Chunk; pub use self::terrain_util::Block; pub use bevy::render::mesh::{Indices, PrimitiveTopology}; pub use bevy::render::render_asset::RenderAssetUsages; @@ -43,10 +45,8 @@ pub use terrain_util::CubeFace; // my crates pub use crate::states::GameState; +pub use lib::*; pub use rsmc as lib; -pub use rsmc::BlockId; -pub use rsmc::NetworkingMessage; -pub use rsmc::CHUNK_SIZE; pub use crate::collider::components as collider_components; pub use crate::collider::events as collider_events; diff --git a/src/client/remote_player/events.rs b/src/client/remote_player/events.rs index 57a560fe..05db15f0 100644 --- a/src/client/remote_player/events.rs +++ b/src/client/remote_player/events.rs @@ -13,5 +13,5 @@ pub struct RemotePlayerDespawnedEvent { #[derive(Event)] pub struct RemotePlayerSyncEvent { - pub players: HashMap, + pub players: HashMap, } diff --git a/src/client/terrain/mod.rs b/src/client/terrain/mod.rs index cabe7d71..0660635e 100644 --- a/src/client/terrain/mod.rs +++ b/src/client/terrain/mod.rs @@ -11,7 +11,7 @@ pub struct TerrainPlugin; impl Plugin for TerrainPlugin { fn build(&self, app: &mut App) { info!("Building TerrainPlugin"); - app.insert_resource(terrain_resources::ChunkManager::new()); + app.insert_resource(ChunkManager::new()); app.insert_resource(terrain_resources::SpawnAreaLoaded(false)); app.insert_resource(util::TextureManager::new()); app.add_event::(); diff --git a/src/client/terrain/resources.rs b/src/client/terrain/resources.rs index e95f60f3..4efbe708 100644 --- a/src/client/terrain/resources.rs +++ b/src/client/terrain/resources.rs @@ -8,197 +8,3 @@ impl SpawnAreaLoaded { resource.0 } } - -#[derive(Resource)] -pub struct ChunkManager { - pub chunks: HashMap<[i32; 3], lib::Chunk>, -} - -impl Default for ChunkManager { - fn default() -> Self { - Self::new() - } -} - -impl ChunkManager { - pub fn new() -> Self { - Self { - chunks: HashMap::new(), - } - } - - pub fn instantiate_chunks(position: Vec3, render_distance: i32) -> Vec { - let mut chunks: Vec = Vec::new(); - - for x in 0..render_distance { - for y in 0..render_distance { - for z in 0..render_distance { - let chunk_position = Vec3::new( - (x - render_distance / 2) as f32 + position.x, - (y - render_distance / 2) as f32 + position.y, - (z - render_distance / 2) as f32 + position.z, - ); - let chunk = lib::Chunk::new(chunk_position); - chunks.push(chunk); - } - } - } - - chunks - } - - pub fn instantiate_new_chunks( - &mut self, - position: Vec3, - render_distance: i32, - ) -> Vec { - let chunks = Self::instantiate_chunks(position, render_distance); - - chunks - .into_iter() - .filter(|chunk| { - let chunk_position = chunk.position; - let chunk = self.get_chunk(chunk_position); - chunk.is_some() - }) - .collect() - } - - pub fn insert_chunk(&mut self, chunk: lib::Chunk) { - self.chunks - .insert(Self::position_to_key(chunk.position), chunk); - } - - pub fn insert_chunks(&mut self, chunks: Vec) { - for chunk in chunks { - self.insert_chunk(chunk); - } - } - - pub fn position_to_key(position: Vec3) -> [i32; 3] { - [position.x as i32, position.y as i32, position.z as i32] - } - - pub fn set_chunk(&mut self, position: Vec3, chunk: lib::Chunk) { - let Vec3 { x, y, z } = position; - - self.chunks.insert([x as i32, y as i32, z as i32], chunk); - } - - pub fn get_chunk(&mut self, position: Vec3) -> Option<&mut lib::Chunk> { - let Vec3 { x, y, z } = position.floor(); - - self.chunks.get_mut(&[x as i32, y as i32, z as i32]) - } - - pub fn set_block(&mut self, position: Vec3, block: BlockId) { - match self.chunk_from_selection(position) { - Some(chunk) => { - let chunk_position = Vec3::new( - chunk.position[0] * CHUNK_SIZE as f32, - chunk.position[1] * CHUNK_SIZE as f32, - chunk.position[2] * CHUNK_SIZE as f32, - ); - let local_position = (position - chunk_position).floor(); - chunk.set( - local_position.x as usize, - local_position.y as usize, - local_position.z as usize, - block, - ); - } - None => { - println!("No chunk found"); - } - } - } - - pub fn get_block(&mut self, position: Vec3) -> Option { - match self.chunk_from_selection(position) { - Some(chunk) => { - let chunk_position = Vec3::new( - chunk.position[0] * CHUNK_SIZE as f32, - chunk.position[1] * CHUNK_SIZE as f32, - chunk.position[2] * CHUNK_SIZE as f32, - ); - let local_position = (position - chunk_position).floor(); - Some(chunk.get( - local_position.x as usize, - local_position.y as usize, - local_position.z as usize, - )) - } - None => { - // println!("No chunk found for block at {:?}", position); - None - } - } - } - - fn chunk_from_selection(&mut self, position: Vec3) -> Option<&mut lib::Chunk> { - let chunk_position = position / CHUNK_SIZE as f32; - self.get_chunk(chunk_position) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_chunk_manager_new() { - let chunk_manager = ChunkManager::new(); - assert!(chunk_manager.chunks.is_empty()); - } - - #[test] - fn test_instantiate_chunks() { - let position = Vec3::new(0.0, 0.0, 0.0); - let render_distance = 2; - let chunks = ChunkManager::instantiate_chunks(position, render_distance); - assert_eq!( - chunks.len(), - (render_distance * render_distance * render_distance) as usize - ); - } - - #[test] - fn test_insert_chunks() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let render_distance = 2; - let chunks = ChunkManager::instantiate_chunks(position, render_distance); - - chunk_manager.insert_chunks(chunks); - assert_eq!( - chunk_manager.chunks.len(), - (render_distance * render_distance * render_distance) as usize - ); - } - - #[test] - fn test_set_and_get_chunk() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let chunk = Chunk::new(position); - - chunk_manager.set_chunk(position, chunk); - let retrieved_chunk = chunk_manager.get_chunk(position).unwrap(); - assert_eq!(retrieved_chunk.position, chunk.position); - } - - #[test] - fn test_set_and_get_block() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let chunk = Chunk::new(position); - - chunk_manager.set_chunk(position, chunk); - let block_position = Vec3::new(1.0, 1.0, 1.0); - let block_id = BlockId::Stone; - - chunk_manager.set_block(block_position, block_id); - let retrieved_block = chunk_manager.get_block(block_position).unwrap(); - assert_eq!(retrieved_block, block_id); - } -} diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index 363548e2..8fcf0ed9 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -1,14 +1,9 @@ use crate::prelude::*; pub fn prepare_spawn_area_system(mut client: ResMut) { - let render_distance = 2; - info!("Sending chunk requests for spawn area"); - let chunks = terrain_resources::ChunkManager::instantiate_chunks( - Vec3::new(0.0, 0.0, 0.0), - render_distance, - ); + let chunks = ChunkManager::instantiate_chunks(Vec3::ZERO, Vec3::ONE); let positions: Vec = chunks.into_iter().map(|chunk| chunk.position).collect(); let message = bincode::serialize(&NetworkingMessage::ChunkBatchRequest(positions)); @@ -18,9 +13,9 @@ pub fn prepare_spawn_area_system(mut client: ResMut) { pub fn generate_world_system( mut client: ResMut, - mut chunk_manager: ResMut, + mut chunk_manager: ResMut, ) { - let render_distance = 6; + let render_distance = Vec3::new(4.0, 4.0, 4.0); info!("Sending chunk requests for chunks"); @@ -28,16 +23,17 @@ pub fn generate_world_system( let positions: Vec = chunks.into_iter().map(|chunk| chunk.position).collect(); - let batched_positions = positions.chunks(32); + let batched_positions = positions.chunks(16); + assert!(batched_positions.len() > 0, "Batched positions is empty"); - batched_positions.for_each(|batch| { + batched_positions.enumerate().for_each(|(index, batch)| { let request_positions = batch.to_vec(); info!( "Sending chunk batch request for {:?}", request_positions.len() ); let message = bincode::serialize(&NetworkingMessage::ChunkBatchRequest(request_positions)); - info!("requesting chunks"); + info!("requesting chunks #{}", index); client.send_message(DefaultChannel::ReliableUnordered, message.unwrap()); }); } @@ -48,7 +44,7 @@ pub fn handle_chunk_mesh_update_events( asset_server: Res, mut meshes: ResMut>, mut materials: ResMut>, - mut chunk_manager: ResMut, + chunk_manager: ResMut, mut chunk_mesh_update_events: EventReader, mut mesh_query: Query<(Entity, &terrain_components::ChunkMesh)>, texture_manager: ResMut, @@ -62,7 +58,7 @@ pub fn handle_chunk_mesh_update_events( match chunk_option { Some(chunk) => { for (entity, chunk_mesh) in mesh_query.iter_mut() { - if lib::Chunk::key_eq_pos(chunk_mesh.key, chunk.position) { + if Chunk::key_eq_pos(chunk_mesh.key, chunk.position) { commands.entity(entity).despawn(); } } @@ -87,7 +83,7 @@ fn add_chunk_objects( asset_server: &Res, meshes: &mut ResMut>, materials: &mut ResMut>, - chunk: &lib::Chunk, + chunk: &Chunk, texture_manager: &terrain_util::TextureManager, ) { if let Some(mesh) = create_chunk_mesh(chunk, texture_manager) { @@ -104,7 +100,7 @@ fn add_chunk_objects( } fn create_chunk_mesh( - chunk: &lib::Chunk, + chunk: &Chunk, texture_manager: &terrain_util::TextureManager, ) -> Option { terrain_util::create_chunk_mesh(chunk, texture_manager) @@ -146,7 +142,7 @@ fn spawn_chunk( meshes: &mut Mut>, material: Handle, mesh: Mesh, - chunk: &lib::Chunk, + chunk: &Chunk, ) { commands.spawn(( Mesh3d(meshes.add(mesh)), diff --git a/src/client/terrain/util/mod.rs b/src/client/terrain/util/mod.rs index ebc1f93e..664bacd1 100644 --- a/src/client/terrain/util/mod.rs +++ b/src/client/terrain/util/mod.rs @@ -1,9 +1,5 @@ pub mod blocks; -pub mod buffer_serializer; -pub mod chunk; pub mod mesher; pub use blocks::*; -pub use buffer_serializer::*; -pub use chunk::*; pub use mesher::*; diff --git a/src/lib.rs b/src/lib.rs index 0525c30c..3c5e6216 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,149 +1,2 @@ -use std::collections::HashMap; - -use bevy::math::{Quat, Vec3}; -use chrono::DateTime; -use renet::ClientId; -use serde::{Deserialize, Serialize}; - -pub const SERVER_MESSAGE_ID: ClientId = 0; - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct PlayerState { - pub position: Vec3, - pub rotation: Quat, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ChatMessage { - pub client_id: ClientId, - pub message_id: usize, - pub timestamp: i64, - pub message: String, -} - -impl ChatMessage { - pub fn format_string(&self) -> String { - let dt = DateTime::from_timestamp_millis(self.timestamp).expect("invalid timestamp"); - let timestamp_string = dt.to_string(); - - let client_name = match self.client_id { - SERVER_MESSAGE_ID => "SERVER".to_string(), - _ => self.client_id.to_string(), - }; - - format!("[{}] {}: {}", timestamp_string, client_name, self.message) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum NetworkingMessage { - PlayerJoin(ClientId), - PlayerLeave(ClientId), - PlayerUpdate(PlayerState), - PlayerSync(HashMap), - ChunkBatchRequest(Vec), - ChunkBatchResponse(Vec), - ChatMessageSend(String), - SingleChatMessageSync(ChatMessage), - ChatMessageSync(Vec), - BlockUpdate { position: Vec3, block: BlockId }, -} - -macro_rules! enum_from_u8 { - ($name:ident { $( $variant:ident ),* $(,)? }) => { - #[repr(u8)] - #[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)] - pub enum $name { - $( $variant ),* - } - - impl $name { - pub fn from_u8(value: u8) -> Option<$name> { - match value { - $(x if x == $name::$variant as u8 => Some($name::$variant),)* - _ => None, - } - } - - pub fn to_u8(&self) -> u8 { - self.clone() as u8 - } - } - }; -} - -enum_from_u8! { - BlockId { - Air, - Grass, - Dirt, - Stone, - Bedrock, - RedSand, - BrownTerracotta, - CyanTerracotta, - GrayTerracotta, - LightGrayTerracotta, - OrangeTerracotta, - RedTerracotta, - Terracotta, - YellowTerracotta, - } -} - -use serde_big_array::BigArray; - -pub const CHUNK_SIZE: usize = 32; -pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; -pub const PADDED_CHUNK_USIZE: usize = PADDED_CHUNK_SIZE; -pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE; - -#[derive(Debug, Clone, Serialize, Deserialize, Copy)] -pub struct Chunk { - #[serde(with = "BigArray")] - pub data: [BlockId; CHUNK_LENGTH], - pub position: Vec3, -} - -impl Chunk { - pub fn new(position: Vec3) -> Self { - Self { - data: [BlockId::Air; CHUNK_LENGTH], - position, - } - } - - pub fn get(&self, x: usize, y: usize, z: usize) -> BlockId { - self.get_unpadded(x + 1, y + 1, z + 1) - } - - pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { - self.data[Self::index(x, y, z)] - } - - pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockId) { - self.set_unpadded(x + 1, y + 1, z + 1, value); - } - - pub fn set_unpadded(&mut self, x: usize, y: usize, z: usize, value: BlockId) { - self.data[Self::index(x, y, z)] = value; - } - - #[rustfmt::skip] - pub fn index(x: usize, y: usize, z: usize) -> usize { - if (x >= PADDED_CHUNK_SIZE) || (y >= PADDED_CHUNK_SIZE) || (z >= PADDED_CHUNK_SIZE) { - panic!("Index out of bounds: ({}, {}, {})", x, y, z); - } - x + PADDED_CHUNK_USIZE * (y + PADDED_CHUNK_USIZE * z) - } - - pub fn key_eq_pos(key: [i32; 3], position: Vec3) -> bool { - position.x as i32 == key[0] && position.y as i32 == key[1] && position.z as i32 == key[2] - } -} - -impl Default for Chunk { - fn default() -> Self { - Self::new(Vec3::ZERO) - } -} +pub mod shared; +pub use shared::*; diff --git a/src/server/chat/resources.rs b/src/server/chat/resources.rs index 7d7d1ddd..a4279fd8 100644 --- a/src/server/chat/resources.rs +++ b/src/server/chat/resources.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Resource, Debug)] pub struct ChatHistory { - pub messages: Vec, + pub messages: Vec, } impl ChatHistory { diff --git a/src/server/chat/systems.rs b/src/server/chat/systems.rs index bb63a9c0..4ced1d07 100644 --- a/src/server/chat/systems.rs +++ b/src/server/chat/systems.rs @@ -15,7 +15,7 @@ pub fn sync_single_player_chat_messages_system( let message_count = chat_messages.messages.len(); let message_id = message_count; - let chat_message = lib::ChatMessage { + let chat_message = ChatMessage { client_id, message_id, message, @@ -24,7 +24,7 @@ pub fn sync_single_player_chat_messages_system( chat_messages.messages.push(chat_message.clone()); - let response_message = lib::NetworkingMessage::SingleChatMessageSync(chat_message); + let response_message = NetworkingMessage::SingleChatMessageSync(chat_message); server.broadcast_message( DefaultChannel::ReliableOrdered, @@ -44,7 +44,7 @@ pub fn sync_player_chat_messages_event( let history = chat_messages.messages.clone(); let response_message = - bincode::serialize(&lib::NetworkingMessage::ChatMessageSync(history)).unwrap(); + bincode::serialize(&NetworkingMessage::ChatMessageSync(history)).unwrap(); server.send_message(client_id, DefaultChannel::ReliableOrdered, response_message); } } diff --git a/src/server/networking/mod.rs b/src/server/networking/mod.rs index c136491b..ac996ffc 100644 --- a/src/server/networking/mod.rs +++ b/src/server/networking/mod.rs @@ -1,7 +1,6 @@ -use std::time::Duration; - pub mod systems; +use crate::connection_config; #[cfg(feature = "renet_visualizer")] use bevy_inspector_egui::bevy_egui::EguiPlugin; #[cfg(feature = "renet_visualizer")] @@ -30,36 +29,9 @@ impl Plugin for NetworkingPlugin { ); } - let channel_config_unreliable = ChannelConfig { - channel_id: 0, - max_memory_usage_bytes: 1000 * 1024 * 1024 * 1024, - send_type: SendType::Unreliable, - }; - - let channel_config_reliable_ordered = ChannelConfig { - channel_id: 1, - max_memory_usage_bytes: 1000 * 1024 * 1024 * 1024, - send_type: SendType::ReliableOrdered { - resend_time: Duration::from_millis(300), - }, - }; - - let channel_config_reliable_unordered = ChannelConfig { - channel_id: 2, - max_memory_usage_bytes: 1000 * 1024 * 1024 * 1024, - send_type: SendType::ReliableUnordered { - resend_time: Duration::from_millis(300), - }, - }; + info!("Config: {:?}", connection_config()); - let server = RenetServer::new(ConnectionConfig { - server_channels_config: Vec::from([ - channel_config_unreliable, - channel_config_reliable_ordered, - channel_config_reliable_unordered, - ]), - ..Default::default() - }); + let server = RenetServer::new(connection_config()); app.insert_resource(server); diff --git a/src/server/networking/systems.rs b/src/server/networking/systems.rs index f08ed2f5..e4b2a96b 100644 --- a/src/server/networking/systems.rs +++ b/src/server/networking/systems.rs @@ -9,7 +9,7 @@ pub fn receive_message_system( mut server: ResMut, mut player_states: ResMut, mut past_block_updates: ResMut, - mut chunk_manager: ResMut, + chunk_manager: ResMut, mut chat_message_events: EventWriter, ) { for client_id in server.clients_id() { @@ -18,7 +18,7 @@ pub fn receive_message_system( let message = bincode::deserialize(&message).unwrap(); match message { - lib::NetworkingMessage::BlockUpdate { position, block } => { + NetworkingMessage::BlockUpdate { position, block } => { info!( "Received block update from client {} {} {:?}", client_id, position, block @@ -30,14 +30,11 @@ pub fn receive_message_system( server.broadcast_message_except( client_id, DefaultChannel::ReliableOrdered, - bincode::serialize(&lib::NetworkingMessage::BlockUpdate { - position, - block, - }) - .unwrap(), + bincode::serialize(&NetworkingMessage::BlockUpdate { position, block }) + .unwrap(), ); } - lib::NetworkingMessage::ChatMessageSend(message) => { + NetworkingMessage::ChatMessageSend(message) => { info!("Received chat message from {}", client_id); chat_message_events .send(chat_events::PlayerChatMessageSendEvent { client_id, message }); @@ -55,28 +52,28 @@ pub fn receive_message_system( debug!("Received message: {:?}", message); match message { - lib::NetworkingMessage::PlayerUpdate(player) => { + NetworkingMessage::PlayerUpdate(player) => { debug!( "Received player update from client {} {}", client_id, player.position ); player_states.players.insert(client_id, player); } - lib::NetworkingMessage::ChunkBatchRequest(positions) => { + NetworkingMessage::ChunkBatchRequest(positions) => { info!( "Received chunk batch request at {:?} from client {}", positions, client_id ); let chunks: Vec = positions - .into_iter() + .into_par_iter() .map(|position| { let chunk = chunk_manager.get_chunk(position); match chunk { Some(chunk) => *chunk, None => { - let mut chunk = lib::Chunk::new(position); + let mut chunk = Chunk::new(position); let generator = terrain_util::generator::Generator::new(0); @@ -89,7 +86,8 @@ pub fn receive_message_system( .collect(); let message = - bincode::serialize(&lib::NetworkingMessage::ChunkBatchResponse(chunks)); + bincode::serialize(&NetworkingMessage::ChunkBatchResponse(chunks)); + server.send_message( client_id, DefaultChannel::ReliableUnordered, @@ -118,7 +116,7 @@ pub fn handle_events_system( println!("Client {client_id} connected"); player_states.players.insert( *client_id, - lib::PlayerState { + PlayerState { position: Vec3::ZERO, rotation: Quat::IDENTITY, }, @@ -129,12 +127,12 @@ pub fn handle_events_system( }); chat_message_events.send(chat_events::PlayerChatMessageSendEvent { - client_id: lib::SERVER_MESSAGE_ID, + client_id: SERVER_MESSAGE_ID, message: format!("Player {} joined the game", *client_id), }); let message = - bincode::serialize(&lib::NetworkingMessage::PlayerJoin(*client_id)).unwrap(); + bincode::serialize(&NetworkingMessage::PlayerJoin(*client_id)).unwrap(); server.broadcast_message_except( *client_id, DefaultChannel::ReliableOrdered, @@ -142,7 +140,7 @@ pub fn handle_events_system( ); for update in past_block_updates.updates.iter() { - let message = bincode::serialize(&lib::NetworkingMessage::BlockUpdate { + let message = bincode::serialize(&NetworkingMessage::BlockUpdate { position: update.position, block: update.block, }) @@ -155,12 +153,12 @@ pub fn handle_events_system( player_states.players.remove(client_id); chat_message_events.send(chat_events::PlayerChatMessageSendEvent { - client_id: lib::SERVER_MESSAGE_ID, + client_id: SERVER_MESSAGE_ID, message: format!("Player {} left the game", client_id), }); let message = - bincode::serialize(&lib::NetworkingMessage::PlayerLeave(*client_id)).unwrap(); + bincode::serialize(&NetworkingMessage::PlayerLeave(*client_id)).unwrap(); server.broadcast_message(DefaultChannel::ReliableOrdered, message); } } diff --git a/src/server/player/resources.rs b/src/server/player/resources.rs index f0d818f2..d184c56c 100644 --- a/src/server/player/resources.rs +++ b/src/server/player/resources.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Resource)] pub struct PlayerStates { - pub players: HashMap, + pub players: HashMap, } impl PlayerStates { diff --git a/src/server/player/systems.rs b/src/server/player/systems.rs index 5e26e0dc..f25bd0ce 100644 --- a/src/server/player/systems.rs +++ b/src/server/player/systems.rs @@ -11,7 +11,7 @@ pub fn broadcast_player_attributes_system( server.send_message( client_id, DefaultChannel::ReliableUnordered, - bincode::serialize(&lib::NetworkingMessage::PlayerSync(other_player_states)).unwrap(), + bincode::serialize(&NetworkingMessage::PlayerSync(other_player_states)).unwrap(), ); } } diff --git a/src/server/prelude.rs b/src/server/prelude.rs index 67699d78..5d28b783 100644 --- a/src/server/prelude.rs +++ b/src/server/prelude.rs @@ -19,16 +19,19 @@ pub use bevy_renet::netcode::ServerConfig; pub use bevy_renet::RenetServerPlugin; pub use renet::DefaultChannel; pub use renet::*; +pub use serde::Deserialize; -pub use self::lib::Chunk; -pub use self::terrain_util::Block; +// other crates +pub use rayon::iter::IntoParallelIterator; +pub use rayon::iter::IntoParallelRefMutIterator; +pub use rayon::iter::ParallelIterator; + +pub use lib::*; pub use noise::NoiseFn; pub use noise::Perlin; - -// my crates pub use rsmc as lib; -pub use rsmc::BlockId; +// my crates pub use crate::networking::systems as networking_systems; pub use crate::player::resources as player_resources; diff --git a/src/server/terrain/mod.rs b/src/server/terrain/mod.rs index 728bb4b6..ae05e3c0 100644 --- a/src/server/terrain/mod.rs +++ b/src/server/terrain/mod.rs @@ -9,7 +9,7 @@ pub struct TerrainPlugin; impl Plugin for TerrainPlugin { fn build(&self, app: &mut App) { - app.insert_resource(terrain_resources::ChunkManager::new()); + app.insert_resource(ChunkManager::new()); app.add_event::(); app.insert_resource(resources::PastBlockUpdates::new()); app.add_systems(Startup, terrain_systems::setup_world_system); diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index 2529e756..c817bb53 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use lib::CHUNK_SIZE; use terrain_events::BlockUpdateEvent; @@ -21,176 +20,3 @@ impl PastBlockUpdates { } } } - -#[derive(Resource)] -pub struct ChunkManager { - pub chunks: HashMap<[i32; 3], lib::Chunk>, -} - -impl Default for ChunkManager { - fn default() -> Self { - Self::new() - } -} - -impl ChunkManager { - pub fn new() -> Self { - Self { - chunks: HashMap::new(), - } - } - - pub fn instantiate_chunks(position: Vec3, render_distance: i32) -> Vec { - let mut chunks: Vec = Vec::new(); - - for x in 0..render_distance { - for y in 0..render_distance { - for z in 0..render_distance { - let chunk_position = Vec3::new( - (x - render_distance / 2) as f32 + position.x, - (y - render_distance / 2) as f32 + position.y, - (z - render_distance / 2) as f32 + position.z, - ); - let chunk = lib::Chunk::new(chunk_position); - chunks.push(chunk); - } - } - } - - chunks - } - - pub fn insert_chunks(&mut self, chunks: Vec) { - for chunk in chunks { - self.chunks - .insert(Self::position_to_key(chunk.position), chunk); - } - } - - pub fn position_to_key(position: Vec3) -> [i32; 3] { - [position.x as i32, position.y as i32, position.z as i32] - } - - pub fn set_chunk(&mut self, position: Vec3, chunk: lib::Chunk) { - let Vec3 { x, y, z } = position; - - self.chunks.insert([x as i32, y as i32, z as i32], chunk); - } - - pub fn get_chunk(&mut self, position: Vec3) -> Option<&mut lib::Chunk> { - let Vec3 { x, y, z } = position.floor(); - - self.chunks.get_mut(&[x as i32, y as i32, z as i32]) - } - - pub fn set_block(&mut self, position: Vec3, block: BlockId) { - match self.chunk_from_selection(position) { - Some(chunk) => { - let chunk_position = Vec3::new( - chunk.position[0] * CHUNK_SIZE as f32, - chunk.position[1] * CHUNK_SIZE as f32, - chunk.position[2] * CHUNK_SIZE as f32, - ); - let local_position = (position - chunk_position).floor(); - chunk.set( - local_position.x as usize, - local_position.y as usize, - local_position.z as usize, - block, - ); - } - None => { - // println!("No chunk found"); - } - } - } - - pub fn get_block(&mut self, position: Vec3) -> Option { - match self.chunk_from_selection(position) { - Some(chunk) => { - let chunk_position = Vec3::new( - chunk.position[0] * CHUNK_SIZE as f32, - chunk.position[1] * CHUNK_SIZE as f32, - chunk.position[2] * CHUNK_SIZE as f32, - ); - let local_position = (position - chunk_position).floor(); - Some(chunk.get( - local_position.x as usize, - local_position.y as usize, - local_position.z as usize, - )) - } - None => { - // println!("No chunk found for block at {:?}", position); - None - } - } - } - - fn chunk_from_selection(&mut self, position: Vec3) -> Option<&mut lib::Chunk> { - let chunk_position = position / CHUNK_SIZE as f32; - self.get_chunk(chunk_position) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_chunk_manager_new() { - let chunk_manager = ChunkManager::new(); - assert!(chunk_manager.chunks.is_empty()); - } - - #[test] - fn test_instantiate_chunks() { - let position = Vec3::new(0.0, 0.0, 0.0); - let render_distance = 2; - let chunks = ChunkManager::instantiate_chunks(position, render_distance); - assert_eq!( - chunks.len(), - (render_distance * render_distance * render_distance) as usize - ); - } - - #[test] - fn test_insert_chunks() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let render_distance = 2; - let chunks = ChunkManager::instantiate_chunks(position, render_distance); - - chunk_manager.insert_chunks(chunks); - assert_eq!( - chunk_manager.chunks.len(), - (render_distance * render_distance * render_distance) as usize - ); - } - - #[test] - fn test_set_and_get_chunk() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let chunk = Chunk::new(position); - - chunk_manager.set_chunk(position, chunk); - let retrieved_chunk = chunk_manager.get_chunk(position).unwrap(); - assert_eq!(retrieved_chunk.position, chunk.position); - } - - #[test] - fn test_set_and_get_block() { - let mut chunk_manager = ChunkManager::new(); - let position = Vec3::new(0.0, 0.0, 0.0); - let chunk = Chunk::new(position); - - chunk_manager.set_chunk(position, chunk); - let block_position = Vec3::new(1.0, 1.0, 1.0); - let block_id = BlockId::Stone; - - chunk_manager.set_block(block_position, block_id); - let retrieved_block = chunk_manager.get_block(block_position).unwrap(); - assert_eq!(retrieved_block, block_id); - } -} diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index 43ad9115..83708e96 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -1,19 +1,13 @@ -use rayon::iter::IntoParallelRefMutIterator; -use rayon::iter::ParallelIterator; - use crate::prelude::*; -pub fn setup_world_system(mut chunk_manager: ResMut) { +pub fn setup_world_system(mut chunk_manager: ResMut) { let generator = terrain_util::generator::Generator::new(0); - let render_distance = 16; + let render_distance = Vec3::new(12.0, 2.0, 12.0); info!("Generating chunks"); - let mut chunks = terrain_resources::ChunkManager::instantiate_chunks( - Vec3::new(0.0, 0.0, 0.0), - render_distance, - ); + let mut chunks = ChunkManager::instantiate_chunks(Vec3::ZERO, render_distance); chunks.par_iter_mut().for_each(|chunk| { info!("Generating chunk at {:?}", chunk.position); diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index 786b8c39..18953009 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -1,5 +1,4 @@ use crate::prelude::*; -use lib::CHUNK_SIZE; pub struct Generator { pub seed: u32, diff --git a/src/shared/blocks.rs b/src/shared/blocks.rs new file mode 100644 index 00000000..6788c950 --- /dev/null +++ b/src/shared/blocks.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +macro_rules! enum_from_u8 { + ($name:ident { $( $variant:ident ),* $(,)? }) => { + #[repr(u8)] + #[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize)] + pub enum $name { + $( $variant ),* + } + + impl $name { + pub fn from_u8(value: u8) -> Option<$name> { + match value { + $(x if x == $name::$variant as u8 => Some($name::$variant),)* + _ => None, + } + } + + pub fn to_u8(&self) -> u8 { + self.clone() as u8 + } + } + }; +} + +enum_from_u8! { + BlockId { + Air, + Grass, + Dirt, + Stone, + Bedrock, + RedSand, + BrownTerracotta, + CyanTerracotta, + GrayTerracotta, + LightGrayTerracotta, + OrangeTerracotta, + RedTerracotta, + Terracotta, + YellowTerracotta, + } +} diff --git a/src/client/terrain/util/buffer_serializer.rs b/src/shared/buffer_serializer.rs similarity index 100% rename from src/client/terrain/util/buffer_serializer.rs rename to src/shared/buffer_serializer.rs diff --git a/src/client/terrain/util/chunk.rs b/src/shared/chunk_serializer.rs similarity index 51% rename from src/client/terrain/util/chunk.rs rename to src/shared/chunk_serializer.rs index 1daf50d1..ade60dfd 100644 --- a/src/client/terrain/util/chunk.rs +++ b/src/shared/chunk_serializer.rs @@ -1,62 +1,12 @@ -use super::buffer_serializer::{deserialize_buffer, serialize_buffer}; -use crate::prelude::*; +use crate::deserialize_buffer; +use crate::serialize_buffer; +use crate::BlockId; +use crate::Chunk; +use crate::CHUNK_LENGTH; +use bevy::math::Vec3; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; -pub const CHUNK_SIZE: usize = 32; -pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; -pub const PADDED_CHUNK_USIZE: usize = PADDED_CHUNK_SIZE; -pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE; - -#[derive(Clone)] -pub struct Chunk { - pub data: [BlockId; CHUNK_LENGTH], - pub position: Vec3, -} - -impl Chunk { - pub fn new(position: Vec3) -> Self { - Self { - data: [BlockId::Air; CHUNK_LENGTH], - position, - } - } - - pub fn get(&self, x: usize, y: usize, z: usize) -> BlockId { - self.get_unpadded(x + 1, y + 1, z + 1) - } - - pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { - self.data[Self::index(x, y, z)] - } - - pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockId) { - self.set_unpadded(x + 1, y + 1, z + 1, value); - } - - pub fn set_unpadded(&mut self, x: usize, y: usize, z: usize, value: BlockId) { - self.data[Self::index(x, y, z)] = value; - } - - #[rustfmt::skip] - pub fn index(x: usize, y: usize, z: usize) -> usize { - if (x >= PADDED_CHUNK_SIZE) || (y >= PADDED_CHUNK_SIZE) || (z >= PADDED_CHUNK_SIZE) { - panic!("Index out of bounds: ({}, {}, {})", x, y, z); - } - x + PADDED_CHUNK_USIZE * (y + PADDED_CHUNK_USIZE * z) - } - - pub fn key_eq_pos(key: [i32; 3], position: Vec3) -> bool { - position.x as i32 == key[0] && position.y as i32 == key[1] && position.z as i32 == key[2] - } -} - -impl Default for Chunk { - fn default() -> Self { - Self::new(Vec3::ZERO) - } -} - impl Serialize for Chunk { fn serialize(&self, serializer: S) -> Result where diff --git a/src/shared/mod.rs b/src/shared/mod.rs new file mode 100644 index 00000000..ddbca847 --- /dev/null +++ b/src/shared/mod.rs @@ -0,0 +1,10 @@ +pub mod blocks; +pub mod buffer_serializer; +pub mod chunk_serializer; +pub mod networking; +pub mod terrain; + +pub use blocks::*; +pub use buffer_serializer::*; +pub use networking::*; +pub use terrain::*; diff --git a/src/shared/networking.rs b/src/shared/networking.rs new file mode 100644 index 00000000..5857faef --- /dev/null +++ b/src/shared/networking.rs @@ -0,0 +1,82 @@ +use std::time::Duration; + +use bevy::math::{Quat, Vec3}; +use chrono::DateTime; +use renet::{ChannelConfig, ClientId, ConnectionConfig, SendType}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use super::{BlockId, Chunk}; + +pub const SERVER_MESSAGE_ID: ClientId = 0; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PlayerState { + pub position: Vec3, + pub rotation: Quat, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ChatMessage { + pub client_id: ClientId, + pub message_id: usize, + pub timestamp: i64, + pub message: String, +} + +impl ChatMessage { + pub fn format_string(&self) -> String { + let dt = DateTime::from_timestamp_millis(self.timestamp).expect("invalid timestamp"); + let timestamp_string = dt.to_string(); + + let client_name = match self.client_id { + SERVER_MESSAGE_ID => "SERVER".to_string(), + _ => self.client_id.to_string(), + }; + + format!("[{}] {}: {}", timestamp_string, client_name, self.message) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum NetworkingMessage { + PlayerJoin(ClientId), + PlayerLeave(ClientId), + PlayerUpdate(PlayerState), + PlayerSync(HashMap), + ChunkBatchRequest(Vec), + ChunkBatchResponse(Vec), + ChatMessageSend(String), + SingleChatMessageSync(ChatMessage), + ChatMessageSync(Vec), + BlockUpdate { position: Vec3, block: BlockId }, +} + +const CHANNELS: [ChannelConfig; 3] = [ + ChannelConfig { + channel_id: 0, + max_memory_usage_bytes: 1024 * 1024 * 1024 * 1024, + send_type: SendType::Unreliable, + }, + ChannelConfig { + channel_id: 1, + max_memory_usage_bytes: 1024 * 1024 * 1024 * 1024, + send_type: SendType::ReliableOrdered { + resend_time: Duration::from_millis(300), + }, + }, + ChannelConfig { + channel_id: 2, + max_memory_usage_bytes: 1024 * 1024 * 1024 * 1024, + send_type: SendType::ReliableUnordered { + resend_time: Duration::from_millis(300), + }, + }, +]; + +pub fn connection_config() -> ConnectionConfig { + ConnectionConfig { + client_channels_config: CHANNELS.to_vec(), + ..Default::default() + } +} diff --git a/src/shared/terrain.rs b/src/shared/terrain.rs new file mode 100644 index 00000000..8437828a --- /dev/null +++ b/src/shared/terrain.rs @@ -0,0 +1,272 @@ +use std::collections::HashMap; + +use bevy::{math::Vec3, prelude::Resource}; + +use super::BlockId; + +pub const CHUNK_SIZE: usize = 32; +pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; +pub const PADDED_CHUNK_USIZE: usize = PADDED_CHUNK_SIZE; +pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE; + +#[derive(Debug, Clone, Copy)] +pub struct Chunk { + // #[serde(with = "BigArray")] + pub data: [BlockId; CHUNK_LENGTH], + pub position: Vec3, +} + +impl Chunk { + pub fn new(position: Vec3) -> Self { + Self { + data: [BlockId::Air; CHUNK_LENGTH], + position, + } + } + + pub fn get(&self, x: usize, y: usize, z: usize) -> BlockId { + self.get_unpadded(x + 1, y + 1, z + 1) + } + + pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { + self.data[Self::index(x, y, z)] + } + + pub fn set(&mut self, x: usize, y: usize, z: usize, value: BlockId) { + self.set_unpadded(x + 1, y + 1, z + 1, value); + } + + pub fn set_unpadded(&mut self, x: usize, y: usize, z: usize, value: BlockId) { + self.data[Self::index(x, y, z)] = value; + } + + #[rustfmt::skip] + pub fn index(x: usize, y: usize, z: usize) -> usize { + if (x >= PADDED_CHUNK_SIZE) || (y >= PADDED_CHUNK_SIZE) || (z >= PADDED_CHUNK_SIZE) { + panic!("Index out of bounds: ({}, {}, {})", x, y, z); + } + x + PADDED_CHUNK_USIZE * (y + PADDED_CHUNK_USIZE * z) + } + + pub fn key_eq_pos(key: [i32; 3], position: Vec3) -> bool { + position.x as i32 == key[0] && position.y as i32 == key[1] && position.z as i32 == key[2] + } +} + +impl Default for Chunk { + fn default() -> Self { + Self::new(Vec3::ZERO) + } +} + +#[derive(Resource)] +pub struct ChunkManager { + pub chunks: HashMap<[i32; 3], Chunk>, +} + +impl Default for ChunkManager { + fn default() -> Self { + Self::new() + } +} + +impl ChunkManager { + pub fn new() -> Self { + Self { + chunks: HashMap::new(), + } + } + + pub fn instantiate_chunks(position: Vec3, render_distance: Vec3) -> Vec { + let render_distance_x = render_distance.x as i32; + let render_distance_y = render_distance.y as i32; + let render_distance_z = render_distance.z as i32; + + let mut chunks: Vec = Vec::new(); + + for x in -render_distance_x..render_distance_x { + for y in -render_distance_y..render_distance_y { + for z in -render_distance_z..render_distance_z { + let chunk_position = Vec3::new( + x as f32 + position.x, + y as f32 + position.y, + z as f32 + position.z, + ); + let chunk = Chunk::new(chunk_position); + chunks.push(chunk); + } + } + } + + chunks + } + + pub fn instantiate_new_chunks(&mut self, position: Vec3, render_distance: Vec3) -> Vec { + let chunks = Self::instantiate_chunks(position, render_distance); + + chunks + .into_iter() + .filter(|chunk| { + let chunk_position = chunk.position; + let chunk = self.get_chunk_mut(chunk_position); + chunk.is_none() + }) + .collect() + } + + pub fn insert_chunk(&mut self, chunk: Chunk) { + self.chunks + .insert(Self::position_to_key(chunk.position), chunk); + } + + pub fn insert_chunks(&mut self, chunks: Vec) { + for chunk in chunks { + self.insert_chunk(chunk); + } + } + + pub fn position_to_key(position: Vec3) -> [i32; 3] { + [position.x as i32, position.y as i32, position.z as i32] + } + + pub fn set_chunk(&mut self, position: Vec3, chunk: Chunk) { + let Vec3 { x, y, z } = position; + + self.chunks.insert([x as i32, y as i32, z as i32], chunk); + } + + pub fn get_chunk(&self, position: Vec3) -> Option<&Chunk> { + let Vec3 { x, y, z } = position.floor(); + + self.chunks.get(&[x as i32, y as i32, z as i32]) + } + + pub fn get_chunk_mut(&mut self, position: Vec3) -> Option<&mut Chunk> { + let Vec3 { x, y, z } = position.floor(); + + self.chunks.get_mut(&[x as i32, y as i32, z as i32]) + } + + pub fn set_block(&mut self, position: Vec3, block: BlockId) { + match self.chunk_from_selection(position) { + Some(chunk) => { + let chunk_position = Vec3::new( + chunk.position[0] * CHUNK_SIZE as f32, + chunk.position[1] * CHUNK_SIZE as f32, + chunk.position[2] * CHUNK_SIZE as f32, + ); + let local_position = (position - chunk_position).floor(); + chunk.set( + local_position.x as usize, + local_position.y as usize, + local_position.z as usize, + block, + ); + } + None => { + println!("No chunk found"); + } + } + } + + pub fn get_block(&mut self, position: Vec3) -> Option { + match self.chunk_from_selection(position) { + Some(chunk) => { + let chunk_position = Vec3::new( + chunk.position[0] * CHUNK_SIZE as f32, + chunk.position[1] * CHUNK_SIZE as f32, + chunk.position[2] * CHUNK_SIZE as f32, + ); + let local_position = (position - chunk_position).floor(); + Some(chunk.get( + local_position.x as usize, + local_position.y as usize, + local_position.z as usize, + )) + } + None => { + // println!("No chunk found for block at {:?}", position); + None + } + } + } + + fn chunk_from_selection(&mut self, position: Vec3) -> Option<&mut Chunk> { + let chunk_position = position / CHUNK_SIZE as f32; + self.get_chunk_mut(chunk_position) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_chunk_manager_new() { + let chunk_manager = ChunkManager::new(); + assert!(chunk_manager.chunks.is_empty()); + } + + #[test] + fn test_instantiate_chunks() { + let position = Vec3::new(0.0, 0.0, 0.0); + + let width = 2; + let height = 3; + let depth = 4; + + let render_distance = Vec3::new(width as f32, height as f32, depth as f32); + + let chunks = ChunkManager::instantiate_chunks(position, render_distance); + assert_eq!(chunks.len(), (2 * width * 2 * height * 2 * depth) as usize,); + } + + #[test] + fn test_insert_chunks() { + let mut chunk_manager = ChunkManager::new(); + let position = Vec3::new(0.0, 0.0, 0.0); + let render_distance = 2; + let chunks = ChunkManager::instantiate_chunks( + position, + Vec3::new( + render_distance as f32, + render_distance as f32, + render_distance as f32, + ), + ); + + let render_diameter = render_distance * 2; + + chunk_manager.insert_chunks(chunks); + assert_eq!( + chunk_manager.chunks.len(), + (render_diameter * render_diameter * render_diameter) as usize + ); + } + + #[test] + fn test_set_and_get_chunk_mut() { + let mut chunk_manager = ChunkManager::new(); + let position = Vec3::new(0.0, 0.0, 0.0); + let chunk = Chunk::new(position); + + chunk_manager.set_chunk(position, chunk); + let retrieved_chunk = chunk_manager.get_chunk_mut(position).unwrap(); + assert_eq!(retrieved_chunk.position, chunk.position); + } + + #[test] + fn test_set_and_get_block() { + let mut chunk_manager = ChunkManager::new(); + let position = Vec3::new(0.0, 0.0, 0.0); + let chunk = Chunk::new(position); + + chunk_manager.set_chunk(position, chunk); + let block_position = Vec3::new(1.0, 1.0, 1.0); + let block_id = BlockId::Stone; + + chunk_manager.set_block(block_position, block_id); + let retrieved_block = chunk_manager.get_block(block_position).unwrap(); + assert_eq!(retrieved_block, block_id); + } +}