From b1db5ef488931da3fea04b36c0911f263f636165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Sun, 4 Oct 2020 20:20:18 +0200 Subject: [PATCH 1/9] Redo Network Frontend. Rather than having a single Stream to handle ALL data, seperate into multiple streams: - Ping Stream, for seperate PINGS - Register Stream, only used till the client is registered, then no longer used! - General Stream, used for msg that can occur always - NotInGame Stream, used for everything NOT ingame, e.g. Character Screen - InGame Stream, used for all GAME data, players, terrain, entities, etc... This version does compile, and gets the client registered (with auth too) but doesnt get to the char screen yet. This fixes also the ignoring messages problem we had, as we are not sending data to the register stream! This fixes also the problem that the server had to sleep for the Stream Creation, as the Server is now creating the streams and client has to sleep. --- client/src/lib.rs | 1276 +++++++++++----------- common/src/msg/client.rs | 51 +- common/src/msg/mod.rs | 16 +- common/src/msg/server.rs | 63 +- server/src/client.rs | 95 +- server/src/cmd.rs | 6 +- server/src/events/entity_manipulation.rs | 4 +- server/src/events/group_manip.rs | 52 +- server/src/events/interaction.rs | 2 +- server/src/events/inventory_manip.rs | 6 +- server/src/events/player.rs | 7 +- server/src/lib.rs | 160 +-- server/src/state_ext.rs | 26 +- server/src/sys/entity_sync.rs | 24 +- server/src/sys/invite_timeout.rs | 4 +- server/src/sys/message.rs | 871 +++++++++------ server/src/sys/subscription.rs | 12 +- server/src/sys/terrain.rs | 6 +- server/src/sys/terrain_sync.rs | 8 +- server/src/sys/waypoint.rs | 2 +- voxygen/src/menu/char_selection/mod.rs | 6 +- voxygen/src/session.rs | 10 +- 22 files changed, 1488 insertions(+), 1219 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index fdd3cd795e..74a7526ad7 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,9 +25,11 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, DisconnectReason, - InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, - ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, + validate_chat_msg, ChatMsgValidationError, ClientInGameMsg, ClientIngame, ClientMsg, + ClientNotInGameMsg, ClientRegisterMsg, ClientType, DisconnectReason, InviteAnswer, + Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerInGameMsg, + ServerInfo, ServerInitMsg, ServerMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, + MAX_BYTES_CHAT_MSG, }, outcome::Outcome, recipe::RecipeBook, @@ -41,7 +43,7 @@ use futures_timer::Delay; use futures_util::{select, FutureExt}; use hashbrown::{HashMap, HashSet}; use image::DynamicImage; -use network::{Network, Participant, Pid, Promises, ProtocolAddr, Stream}; +use network::{Network, Participant, Pid, ProtocolAddr, Stream}; use num::traits::FloatConst; use rayon::prelude::*; use std::{ @@ -68,7 +70,8 @@ pub enum Event { } pub struct Client { - client_state: ClientState, + registered: bool, + client_ingame: Option, thread_pool: ThreadPool, pub server_info: ServerInfo, /// Just the "base" layer for LOD; currently includes colors and nothing @@ -111,6 +114,10 @@ pub struct Client { _network: Network, participant: Option, singleton_stream: Stream, + ping_stream: Stream, + register_stream: Stream, + not_in_game_stream: Stream, + in_game_stream: Stream, client_timeout: Duration, last_server_ping: f64, @@ -141,8 +148,6 @@ pub struct CharacterList { impl Client { /// Create a new `Client`. pub fn new>(addr: A, view_distance: Option) -> Result { - let client_state = ClientState::Connected; - let mut thread_pool = ThreadPoolBuilder::new() .name("veloren-worker".into()) .build(); @@ -153,16 +158,33 @@ impl Client { thread_pool.execute(scheduler); let participant = block_on(network.connect(ProtocolAddr::Tcp(addr.into())))?; - let mut stream = block_on(participant.open( - 10, - Promises::ORDERED | Promises::CONSISTENCY | Promises::COMPRESSED, - ))?; + let stream = block_on(participant.opened())?; + let mut ping_stream = block_on(participant.opened())?; + let mut register_stream = block_on(participant.opened())?; + let not_in_game_stream = block_on(participant.opened())?; + let in_game_stream = block_on(participant.opened())?; + + register_stream.send(ClientType::Game)?; + let server_info: ServerInfo = block_on(register_stream.recv())?; + + // TODO: Display that versions don't match in Voxygen + if server_info.git_hash != *common::util::GIT_HASH { + warn!( + "Server is running {}[{}], you are running {}[{}], versions might be incompatible!", + server_info.git_hash, + server_info.git_date, + common::util::GIT_HASH.to_string(), + common::util::GIT_DATE.to_string(), + ); + } + debug!("Auth Server: {:?}", server_info.auth_provider); + + ping_stream.send(PingMsg::Ping)?; // Wait for initial sync let ( state, entity, - server_info, lod_base, lod_alt, lod_horizon, @@ -170,204 +192,179 @@ impl Client { recipe_book, max_group_size, client_timeout, - ) = block_on(async { - loop { - match stream.recv().await? { - ServerMsg::InitialSync { - entity_package, - server_info, - time_of_day, - max_group_size, - client_timeout, - world_map, - recipe_book, - } => { - // TODO: Display that versions don't match in Voxygen - if server_info.git_hash != *common::util::GIT_HASH { - warn!( - "Server is running {}[{}], you are running {}[{}], versions might \ - be incompatible!", - server_info.git_hash, - server_info.git_date, - common::util::GIT_HASH.to_string(), - common::util::GIT_DATE.to_string(), - ); - } + ) = match block_on(register_stream.recv())? { + ServerInitMsg::GameSync { + entity_package, + time_of_day, + max_group_size, + client_timeout, + world_map, + recipe_book, + } => { + // Initialize `State` + let mut state = State::default(); + // Client-only components + state + .ecs_mut() + .register::>(); - debug!("Auth Server: {:?}", server_info.auth_provider); + let entity = state.ecs_mut().apply_entity_package(entity_package); + *state.ecs_mut().write_resource() = time_of_day; - // Initialize `State` - let mut state = State::default(); - // Client-only components - state - .ecs_mut() - .register::>(); - - let entity = state.ecs_mut().apply_entity_package(entity_package); - *state.ecs_mut().write_resource() = time_of_day; - - let map_size_lg = common::terrain::MapSizeLg::new(world_map.dimensions_lg) - .map_err(|_| { - Error::Other(format!( - "Server sent bad world map dimensions: {:?}", - world_map.dimensions_lg, - )) - })?; - let map_size = map_size_lg.chunks(); - let max_height = world_map.max_height; - let sea_level = world_map.sea_level; - let rgba = world_map.rgba; - let alt = world_map.alt; - let expected_size = - (u32::from(map_size.x) * u32::from(map_size.y)) as usize; - if rgba.len() != expected_size { - return Err(Error::Other("Server sent a bad world map image".into())); - } - if alt.len() != expected_size { - return Err(Error::Other("Server sent a bad altitude map.".into())); - } - let [west, east] = world_map.horizons; - let scale_angle = - |a: u8| (a as f32 / 255.0 * ::FRAC_PI_2()).tan(); - let scale_height = |h: u8| h as f32 / 255.0 * max_height; - let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height; - - debug!("Preparing image..."); - let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| { - ( - angles.iter().copied().map(scale_angle).collect::>(), - heights - .iter() - .copied() - .map(scale_height) - .collect::>(), - ) - }; - let horizons = [unzip_horizons(&west), unzip_horizons(&east)]; - - // Redraw map (with shadows this time). - let mut world_map = vec![0u32; rgba.len()]; - let mut map_config = common::terrain::map::MapConfig::orthographic( - map_size_lg, - core::ops::RangeInclusive::new(0.0, max_height), - ); - map_config.horizons = Some(&horizons); - let rescale_height = |h: f32| h / max_height; - let bounds_check = |pos: Vec2| { - pos.reduce_partial_min() >= 0 - && pos.x < map_size.x as i32 - && pos.y < map_size.y as i32 - }; - map_config.generate( - |pos| { - let (rgba, alt, downhill_wpos) = if bounds_check(pos) { - let posi = - pos.y as usize * map_size.x as usize + pos.x as usize; - let [r, g, b, a] = rgba[posi].to_le_bytes(); - let alti = alt[posi]; - // Compute downhill. - let downhill = { - let mut best = -1; - let mut besth = alti; - for nposi in neighbors(map_size_lg, posi) { - let nbh = alt[nposi]; - if nbh < besth { - besth = nbh; - best = nposi as isize; - } - } - best - }; - let downhill_wpos = if downhill < 0 { - None - } else { - Some( - Vec2::new( - (downhill as usize % map_size.x as usize) as i32, - (downhill as usize / map_size.x as usize) as i32, - ) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), - ) - }; - (Rgba::new(r, g, b, a), alti, downhill_wpos) - } else { - (Rgba::zero(), 0, None) - }; - let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); - let downhill_wpos = downhill_wpos.unwrap_or( - wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32), - ); - let alt = rescale_height(scale_height_big(alt)); - common::terrain::map::MapSample { - rgb: Rgb::from(rgba), - alt: f64::from(alt), - downhill_wpos, - connections: None, - } - }, - |wpos| { - let pos = - wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32); - rescale_height(if bounds_check(pos) { - let posi = - pos.y as usize * map_size.x as usize + pos.x as usize; - scale_height_big(alt[posi]) - } else { - 0.0 - }) - }, - |pos, (r, g, b, a)| { - world_map[pos.y * map_size.x as usize + pos.x] = - u32::from_le_bytes([r, g, b, a]); - }, - ); - let make_raw = |rgba| -> Result<_, Error> { - let mut raw = vec![0u8; 4 * world_map.len()]; - LittleEndian::write_u32_into(rgba, &mut raw); - Ok(Arc::new( - image::DynamicImage::ImageRgba8({ - // Should not fail if the dimensions are correct. - let map = - image::ImageBuffer::from_raw(u32::from(map_size.x), u32::from(map_size.y), raw); - map.ok_or_else(|| Error::Other("Server sent a bad world map image".into()))? - }) - // Flip the image, since Voxygen uses an orientation where rotation from - // positive x axis to positive y axis is counterclockwise around the z axis. - .flipv(), - )) - }; - let lod_base = rgba; - let lod_alt = alt; - let world_map = make_raw(&world_map)?; - let horizons = (west.0, west.1, east.0, east.1) - .into_par_iter() - .map(|(wa, wh, ea, eh)| u32::from_le_bytes([wa, wh, ea, eh])) - .collect::>(); - let lod_horizon = horizons; - let map_bounds = Vec2::new(sea_level, max_height); - debug!("Done preparing image..."); - - break Ok(( - state, - entity, - server_info, - lod_base, - lod_alt, - lod_horizon, - (world_map, map_size, map_bounds), - recipe_book, - max_group_size, - client_timeout, - )); - }, - ServerMsg::TooManyPlayers => break Err(Error::TooManyPlayers), - err => { - warn!("whoops, server mad {:?}, ignoring", err); - }, + let map_size_lg = common::terrain::MapSizeLg::new(world_map.dimensions_lg) + .map_err(|_| { + Error::Other(format!( + "Server sent bad world map dimensions: {:?}", + world_map.dimensions_lg, + )) + })?; + let map_size = map_size_lg.chunks(); + let max_height = world_map.max_height; + let sea_level = world_map.sea_level; + let rgba = world_map.rgba; + let alt = world_map.alt; + let expected_size = (u32::from(map_size.x) * u32::from(map_size.y)) as usize; + if rgba.len() != expected_size { + return Err(Error::Other("Server sent a bad world map image".into())); } - } - })?; + if alt.len() != expected_size { + return Err(Error::Other("Server sent a bad altitude map.".into())); + } + let [west, east] = world_map.horizons; + let scale_angle = + |a: u8| (a as f32 / 255.0 * ::FRAC_PI_2()).tan(); + let scale_height = |h: u8| h as f32 / 255.0 * max_height; + let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height; + ping_stream.send(PingMsg::Ping)?; - stream.send(ClientMsg::Ping)?; + debug!("Preparing image..."); + let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| { + ( + angles.iter().copied().map(scale_angle).collect::>(), + heights + .iter() + .copied() + .map(scale_height) + .collect::>(), + ) + }; + let horizons = [unzip_horizons(&west), unzip_horizons(&east)]; + + // Redraw map (with shadows this time). + let mut world_map = vec![0u32; rgba.len()]; + let mut map_config = common::terrain::map::MapConfig::orthographic( + map_size_lg, + core::ops::RangeInclusive::new(0.0, max_height), + ); + map_config.horizons = Some(&horizons); + let rescale_height = |h: f32| h / max_height; + let bounds_check = |pos: Vec2| { + pos.reduce_partial_min() >= 0 + && pos.x < map_size.x as i32 + && pos.y < map_size.y as i32 + }; + ping_stream.send(PingMsg::Ping)?; + map_config.generate( + |pos| { + let (rgba, alt, downhill_wpos) = if bounds_check(pos) { + let posi = pos.y as usize * map_size.x as usize + pos.x as usize; + let [r, g, b, a] = rgba[posi].to_le_bytes(); + let alti = alt[posi]; + // Compute downhill. + let downhill = { + let mut best = -1; + let mut besth = alti; + for nposi in neighbors(map_size_lg, posi) { + let nbh = alt[nposi]; + if nbh < besth { + besth = nbh; + best = nposi as isize; + } + } + best + }; + let downhill_wpos = if downhill < 0 { + None + } else { + Some( + Vec2::new( + (downhill as usize % map_size.x as usize) as i32, + (downhill as usize / map_size.x as usize) as i32, + ) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + ) + }; + (Rgba::new(r, g, b, a), alti, downhill_wpos) + } else { + (Rgba::zero(), 0, None) + }; + let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let downhill_wpos = downhill_wpos + .unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)); + let alt = rescale_height(scale_height_big(alt)); + common::terrain::map::MapSample { + rgb: Rgb::from(rgba), + alt: f64::from(alt), + downhill_wpos, + connections: None, + } + }, + |wpos| { + let pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32); + rescale_height(if bounds_check(pos) { + let posi = pos.y as usize * map_size.x as usize + pos.x as usize; + scale_height_big(alt[posi]) + } else { + 0.0 + }) + }, + |pos, (r, g, b, a)| { + world_map[pos.y * map_size.x as usize + pos.x] = + u32::from_le_bytes([r, g, b, a]); + }, + ); + ping_stream.send(PingMsg::Ping)?; + let make_raw = |rgba| -> Result<_, Error> { + let mut raw = vec![0u8; 4 * world_map.len()]; + LittleEndian::write_u32_into(rgba, &mut raw); + Ok(Arc::new( + image::DynamicImage::ImageRgba8({ + // Should not fail if the dimensions are correct. + let map = + image::ImageBuffer::from_raw(u32::from(map_size.x), u32::from(map_size.y), raw); + map.ok_or_else(|| Error::Other("Server sent a bad world map image".into()))? + }) + // Flip the image, since Voxygen uses an orientation where rotation from + // positive x axis to positive y axis is counterclockwise around the z axis. + .flipv(), + )) + }; + ping_stream.send(PingMsg::Ping)?; + let lod_base = rgba; + let lod_alt = alt; + let world_map = make_raw(&world_map)?; + let horizons = (west.0, west.1, east.0, east.1) + .into_par_iter() + .map(|(wa, wh, ea, eh)| u32::from_le_bytes([wa, wh, ea, eh])) + .collect::>(); + let lod_horizon = horizons; + let map_bounds = Vec2::new(sea_level, max_height); + debug!("Done preparing image..."); + + Ok(( + state, + entity, + lod_base, + lod_alt, + lod_horizon, + (world_map, map_size, map_bounds), + recipe_book, + max_group_size, + client_timeout, + )) + }, + ServerInitMsg::TooManyPlayers => Err(Error::TooManyPlayers), + }?; + ping_stream.send(PingMsg::Ping)?; let mut thread_pool = ThreadPoolBuilder::new() .name("veloren-worker".into()) @@ -375,8 +372,11 @@ impl Client { // We reduce the thread count by 1 to keep rendering smooth thread_pool.set_num_threads((num_cpus::get() - 1).max(1)); + debug!("Initial sync done"); + Ok(Self { - client_state, + registered: false, + client_ingame: None, thread_pool, server_info, world_map, @@ -398,6 +398,11 @@ impl Client { _network: network, participant: Some(participant), singleton_stream: stream, + ping_stream, + register_stream, + not_in_game_stream, + in_game_stream, + client_timeout, last_server_ping: 0.0, @@ -439,72 +444,52 @@ impl Client { } ).unwrap_or(Ok(username))?; - self.singleton_stream.send(ClientMsg::Register { + //TODO move ViewDistance out of register + self.register_stream.send(ClientRegisterMsg { view_distance: self.view_distance, token_or_username, })?; - self.client_state = ClientState::Pending; - block_on(async { - loop { - match self.singleton_stream.recv().await? { - ServerMsg::StateAnswer(Err(( - RequestStateError::RegisterDenied(err), - state, - ))) => { - self.client_state = state; - break Err(match err { - RegisterError::AlreadyLoggedIn => Error::AlreadyLoggedIn, - RegisterError::AuthError(err) => Error::AuthErr(err), - RegisterError::InvalidCharacter => Error::InvalidCharacter, - RegisterError::NotOnWhitelist => Error::NotOnWhitelist, - RegisterError::Banned(reason) => Error::Banned(reason), - }); - }, - ServerMsg::StateAnswer(Ok(ClientState::Registered)) => break Ok(()), - ignore => { - warn!( - "Ignoring what the server send till registered: {:? }", - ignore - ); - //return Err(Error::ServerWentMad) - }, - } - } - }) + match block_on(self.register_stream.recv::())? { + Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn), + Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), + Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), + Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist), + Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)), + Ok(()) => Ok(()), + } } /// Request a state transition to `ClientState::Character`. pub fn request_character(&mut self, character_id: CharacterId) { - self.singleton_stream - .send(ClientMsg::Character(character_id)) + self.not_in_game_stream + .send(ClientNotInGameMsg::Character(character_id)) .unwrap(); self.active_character_id = Some(character_id); - self.client_state = ClientState::Pending; } /// Load the current players character list pub fn load_character_list(&mut self) { self.character_list.loading = true; - self.singleton_stream - .send(ClientMsg::RequestCharacterList) + self.not_in_game_stream + .send(ClientNotInGameMsg::RequestCharacterList) .unwrap(); } /// New character creation pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { self.character_list.loading = true; - self.singleton_stream - .send(ClientMsg::CreateCharacter { alias, tool, body }) + self.not_in_game_stream + .send(ClientNotInGameMsg::CreateCharacter { alias, tool, body }) .unwrap(); } /// Character deletion pub fn delete_character(&mut self, character_id: CharacterId) { self.character_list.loading = true; - self.singleton_stream - .send(ClientMsg::DeleteCharacter(character_id)) + self.not_in_game_stream + .send(ClientNotInGameMsg::DeleteCharacter(character_id)) .unwrap(); } @@ -522,37 +507,40 @@ impl Client { /// Request a state transition to `ClientState::Registered` from an ingame /// state. pub fn request_remove_character(&mut self) { - self.singleton_stream.send(ClientMsg::ExitIngame).unwrap(); - self.client_state = ClientState::Pending; + self.in_game_stream + .send(ClientInGameMsg::ExitInGame) + .unwrap(); } pub fn set_view_distance(&mut self, view_distance: u32) { self.view_distance = Some(view_distance.max(1).min(65)); - self.singleton_stream - .send(ClientMsg::SetViewDistance(self.view_distance.unwrap())) + self.in_game_stream + .send(ClientInGameMsg::SetViewDistance( + self.view_distance.unwrap(), + )) .unwrap(); // Can't fail } pub fn use_slot(&mut self, slot: comp::slot::Slot) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Use(slot), ))) .unwrap(); } pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Swap(a, b), ))) .unwrap(); } pub fn drop_slot(&mut self, slot: comp::slot::Slot) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Drop(slot), ))) .unwrap(); @@ -560,8 +548,8 @@ impl Client { pub fn pick_up(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Pickup(uid), ))) .unwrap(); @@ -582,8 +570,8 @@ impl Client { pub fn craft_recipe(&mut self, recipe: &str) -> bool { if self.can_craft_recipe(recipe) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::CraftRecipe(recipe.to_string()), ))) .unwrap(); @@ -604,13 +592,13 @@ impl Client { pub fn enable_lantern(&mut self) { self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::EnableLantern)) + .send(ClientInGameMsg::ControlEvent(ControlEvent::EnableLantern)) .unwrap(); } pub fn disable_lantern(&mut self) { self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::DisableLantern)) + .send(ClientInGameMsg::ControlEvent(ControlEvent::DisableLantern)) .unwrap(); } @@ -629,8 +617,8 @@ impl Client { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } pub fn send_group_invite(&mut self, invitee: Uid) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::Invite(invitee), ))) .unwrap() @@ -639,8 +627,8 @@ impl Client { pub fn accept_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::Accept, ))) .unwrap(); @@ -649,32 +637,32 @@ impl Client { pub fn decline_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::Decline, ))) .unwrap(); } pub fn leave_group(&mut self) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::Leave, ))) .unwrap(); } pub fn kick_from_group(&mut self, uid: Uid) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::Kick(uid), ))) .unwrap(); } pub fn assign_group_leader(&mut self, uid: Uid) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::GroupManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( GroupManip::AssignLeader(uid), ))) .unwrap(); @@ -698,15 +686,15 @@ impl Client { pub fn mount(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::Mount(uid))) + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::Mount(uid))) .unwrap(); } } pub fn unmount(&mut self) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::Unmount)) + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::Unmount)) .unwrap(); } @@ -718,8 +706,8 @@ impl Client { .get(self.entity) .map_or(false, |s| s.is_dead) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::Respawn)) + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::Respawn)) .unwrap(); } } @@ -817,8 +805,8 @@ impl Client { { controller.actions.push(control_action); } - self.singleton_stream - .send(ClientMsg::ControlAction(control_action)) + self.in_game_stream + .send(ClientInGameMsg::ControlAction(control_action)) .unwrap(); } @@ -866,20 +854,20 @@ impl Client { } pub fn place_block(&mut self, pos: Vec3, block: Block) { - self.singleton_stream - .send(ClientMsg::PlaceBlock(pos, block)) + self.in_game_stream + .send(ClientInGameMsg::PlaceBlock(pos, block)) .unwrap(); } pub fn remove_block(&mut self, pos: Vec3) { - self.singleton_stream - .send(ClientMsg::BreakBlock(pos)) + self.in_game_stream + .send(ClientInGameMsg::BreakBlock(pos)) .unwrap(); } pub fn collect_block(&mut self, pos: Vec3) { - self.singleton_stream - .send(ClientMsg::ControlEvent(ControlEvent::InventoryManip( + self.in_game_stream + .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Collect(pos), ))) .unwrap(); @@ -914,7 +902,7 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. - if let ClientState::Character = self.client_state { + if self.client_ingame.is_some() { if let Err(e) = self .state .ecs() @@ -937,8 +925,8 @@ impl Client { "Couldn't access controller component on client entity" ); } - self.singleton_stream - .send(ClientMsg::ControllerInputs(inputs))?; + self.in_game_stream + .send(ClientInGameMsg::ControllerInputs(inputs))?; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -1042,8 +1030,8 @@ impl Client { if self.state.terrain().get_key(*key).is_none() { if !skip_mode && !self.pending_chunks.contains_key(key) { if self.pending_chunks.len() < 4 { - self.singleton_stream - .send(ClientMsg::TerrainChunkRequest { key: *key })?; + self.in_game_stream + .send(ClientInGameMsg::TerrainChunkRequest { key: *key })?; self.pending_chunks.insert(*key, Instant::now()); } else { skip_mode = true; @@ -1075,19 +1063,19 @@ impl Client { // Send a ping to the server once every second if self.state.get_time() - self.last_server_ping > 1. { - self.singleton_stream.send(ClientMsg::Ping)?; + self.ping_stream.send(PingMsg::Ping)?; self.last_server_ping = self.state.get_time(); } // 6) Update the server about the player's physics attributes. - if let ClientState::Character = self.client_state { + if self.client_ingame.is_some() { if let (Some(pos), Some(vel), Some(ori)) = ( self.state.read_storage().get(self.entity).cloned(), self.state.read_storage().get(self.entity).cloned(), self.state.read_storage().get(self.entity).cloned(), ) { - self.singleton_stream - .send(ClientMsg::PlayerPhysics { pos, vel, ori })?; + self.in_game_stream + .send(ClientInGameMsg::PlayerPhysics { pos, vel, ori })?; } } @@ -1114,343 +1102,368 @@ impl Client { self.state.cleanup(); } - async fn handle_message( + fn handle_server_msg( + &mut self, + frontend_events: &mut Vec, + msg: ServerMsg, + ) -> Result<(), Error> { + match msg { + ServerMsg::Disconnect(reason) => match reason { + DisconnectReason::Shutdown => return Err(Error::ServerShutdown), + DisconnectReason::Requested => { + debug!("finally sending ClientMsg::Terminate"); + frontend_events.push(Event::Disconnect); + self.singleton_stream.send(ClientMsg::Terminate)?; + }, + DisconnectReason::Kicked(reason) => { + debug!("sending ClientMsg::Terminate because we got kicked"); + frontend_events.push(Event::Kicked(reason.clone())); + self.singleton_stream.send(ClientMsg::Terminate)?; + }, + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => self.player_list = list, + ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { + if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) { + warn!( + "Received msg to insert {} with uid {} into the player list but there was \ + already an entry for {} with the same uid that was overwritten!", + player_info.player_alias, uid, old_player_info.player_alias + ); + } + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { + if let Some(player_info) = self.player_list.get_mut(&uid) { + player_info.is_admin = admin; + } else { + warn!( + "Received msg to update admin status of uid {}, but they were not in the \ + list.", + uid + ); + } + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(uid, char_info)) => { + if let Some(player_info) = self.player_list.get_mut(&uid) { + player_info.character = Some(char_info); + } else { + warn!( + "Received msg to update character info for uid {}, but they were not in \ + the list.", + uid + ); + } + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { + if let Some(player_info) = self.player_list.get_mut(&uid) { + player_info.character = match &player_info.character { + Some(character) => Some(common::msg::CharacterInfo { + name: character.name.to_string(), + level: next_level, + }), + None => { + warn!( + "Received msg to update character level info to {} for uid {}, \ + but this player's character is None.", + next_level, uid + ); + + None + }, + }; + } + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { + // Instead of removing players, mark them as offline because we need to + // remember the names of disconnected players in chat. + // + // TODO the server should re-use uids of players that log out and log back + // in. + + if let Some(player_info) = self.player_list.get_mut(&uid) { + if player_info.is_online { + player_info.is_online = false; + } else { + warn!( + "Received msg to remove uid {} from the player list by they were \ + already marked offline", + uid + ); + } + } else { + warn!( + "Received msg to remove uid {} from the player list by they weren't in \ + the list!", + uid + ); + } + }, + ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { + if let Some(player_info) = self.player_list.get_mut(&uid) { + player_info.player_alias = new_name; + } else { + warn!( + "Received msg to alias player with uid {} to {} but this uid is not in \ + the player list", + uid, new_name + ); + } + }, + ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), + ServerMsg::SetPlayerEntity(uid) => { + if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { + self.entity = entity; + } else { + return Err(Error::Other("Failed to find entity from uid.".to_owned())); + } + }, + ServerMsg::TimeOfDay(time_of_day) => { + *self.state.ecs_mut().write_resource() = time_of_day; + }, + ServerMsg::EntitySync(entity_sync_package) => { + self.state + .ecs_mut() + .apply_entity_sync_package(entity_sync_package); + }, + ServerMsg::CompSync(comp_sync_package) => { + self.state + .ecs_mut() + .apply_comp_sync_package(comp_sync_package); + }, + ServerMsg::CreateEntity(entity_package) => { + self.state.ecs_mut().apply_entity_package(entity_package); + }, + ServerMsg::DeleteEntity(entity) => { + if self.uid() != Some(entity) { + self.state + .ecs_mut() + .delete_entity_and_clear_from_uid_allocator(entity.0); + } + }, + ServerMsg::Notification(n) => { + frontend_events.push(Event::Notification(n)); + }, + } + Ok(()) + } + + fn handle_server_in_game_msg( + &mut self, + frontend_events: &mut Vec, + msg: ServerInGameMsg, + ) -> Result<(), Error> { + match msg { + ServerInGameMsg::GroupUpdate(change_notification) => { + use comp::group::ChangeNotification::*; + // Note: we use a hashmap since this would not work with entities outside + // the view distance + match change_notification { + Added(uid, role) => { + // Check if this is a newly formed group by looking for absence of + // other non pet group members + if !matches!(role, group::Role::Pet) + && !self + .group_members + .values() + .any(|r| !matches!(r, group::Role::Pet)) + { + frontend_events + .push(Event::Chat(comp::ChatType::Meta.chat_msg( + "Type /g or /group to chat with your group members", + ))); + } + if let Some(player_info) = self.player_list.get(&uid) { + frontend_events.push(Event::Chat( + comp::ChatType::GroupMeta("Group".into()).chat_msg(format!( + "[{}] joined group", + self.personalize_alias(uid, player_info.player_alias.clone()) + )), + )); + } + if self.group_members.insert(uid, role) == Some(role) { + warn!( + "Received msg to add uid {} to the group members but they were \ + already there", + uid + ); + } + }, + Removed(uid) => { + if let Some(player_info) = self.player_list.get(&uid) { + frontend_events.push(Event::Chat( + comp::ChatType::GroupMeta("Group".into()).chat_msg(format!( + "[{}] left group", + self.personalize_alias(uid, player_info.player_alias.clone()) + )), + )); + } + if self.group_members.remove(&uid).is_none() { + warn!( + "Received msg to remove uid {} from group members but by they \ + weren't in there!", + uid + ); + } + }, + NewLeader(leader) => { + self.group_leader = Some(leader); + }, + NewGroup { leader, members } => { + self.group_leader = Some(leader); + self.group_members = members.into_iter().collect(); + // Currently add/remove messages treat client as an implicit member + // of the group whereas this message explicitly includes them so to + // be consistent for now we will remove the client from the + // received hashset + if let Some(uid) = self.uid() { + self.group_members.remove(&uid); + } + }, + NoGroup => { + self.group_leader = None; + self.group_members = HashMap::new(); + }, + } + }, + ServerInGameMsg::GroupInvite { inviter, timeout } => { + self.group_invite = Some((inviter, std::time::Instant::now(), timeout)); + }, + ServerInGameMsg::InvitePending(uid) => { + if !self.pending_invites.insert(uid) { + warn!("Received message about pending invite that was already pending"); + } + }, + ServerInGameMsg::InviteComplete { target, answer } => { + if !self.pending_invites.remove(&target) { + warn!( + "Received completed invite message for invite that was not in the list of \ + pending invites" + ) + } + // TODO: expose this as a new event variant instead of going + // through the chat + let msg = match answer { + // TODO: say who accepted/declined/timed out the invite + InviteAnswer::Accepted => "Invite accepted", + InviteAnswer::Declined => "Invite declined", + InviteAnswer::TimedOut => "Invite timed out", + }; + frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg))); + }, + // Cleanup for when the client goes back to the `in_game = None` + ServerInGameMsg::ExitInGameSuccess => { + self.clean_state(); + }, + ServerInGameMsg::InventoryUpdate(mut inventory, event) => { + match event { + InventoryUpdateEvent::CollectFailed => {}, + _ => { + inventory.recount_items(); + // Push the updated inventory component to the client + self.state.write_component(self.entity, inventory); + }, + } + + self.update_available_recipes(); + + frontend_events.push(Event::InventoryUpdated(event)); + }, + ServerInGameMsg::TerrainChunkUpdate { key, chunk } => { + if let Ok(chunk) = chunk { + self.state.insert_chunk(key, *chunk); + } + self.pending_chunks.remove(&key); + }, + ServerInGameMsg::TerrainBlockUpdates(mut blocks) => { + blocks.drain().for_each(|(pos, block)| { + self.state.set_block(pos, block); + }); + }, + ServerInGameMsg::SetViewDistance(vd) => { + self.view_distance = Some(vd); + frontend_events.push(Event::SetViewDistance(vd)); + }, + ServerInGameMsg::Outcomes(outcomes) => { + frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) + }, + ServerInGameMsg::Knockback(impulse) => { + self.state + .ecs() + .read_resource::>() + .emit_now(LocalEvent::ApplyImpulse { + entity: self.entity, + impulse, + }); + }, + } + Ok(()) + } + + fn handle_server_not_in_game_msg(&mut self, msg: ServerNotInGameMsg) -> Result<(), Error> { + match msg { + ServerNotInGameMsg::CharacterListUpdate(character_list) => { + self.character_list.characters = character_list; + self.character_list.loading = false; + }, + ServerNotInGameMsg::CharacterActionError(error) => { + warn!("CharacterActionError: {:?}.", error); + self.character_list.error = Some(error); + }, + ServerNotInGameMsg::CharacterDataLoadError(error) => { + self.clean_state(); + self.character_list.error = Some(error); + }, + ServerNotInGameMsg::CharacterSuccess => { + warn!("WOOP88u8yeah"); + }, + } + Ok(()) + } + + fn handle_ping_msg(&mut self, msg: PingMsg) -> Result<(), Error> { + match msg { + PingMsg::Ping => { + self.ping_stream.send(PingMsg::Pong)?; + }, + PingMsg::Pong => { + self.last_server_pong = self.state.get_time(); + self.last_ping_delta = self.state.get_time() - self.last_server_ping; + + // Maintain the correct number of deltas for calculating the rolling average + // ping. The client sends a ping to the server every second so we should be + // receiving a pong reply roughly every second. + while self.ping_deltas.len() > PING_ROLLING_AVERAGE_SECS - 1 { + self.ping_deltas.pop_front(); + } + self.ping_deltas.push_back(self.last_ping_delta); + }, + } + Ok(()) + } + + async fn handle_messages( &mut self, frontend_events: &mut Vec, cnt: &mut u64, ) -> Result<(), Error> { loop { - let msg = self.singleton_stream.recv().await?; + let (m1, m2, m3, m4) = select!( + msg = self.singleton_stream.recv().fuse() => (Some(msg?), None, None, None), + msg = self.ping_stream.recv().fuse() => (None, Some(msg?), None, None), + msg = self.not_in_game_stream.recv().fuse() => (None, None, Some(msg?), None), + msg = self.in_game_stream.recv().fuse() => (None, None, None, Some(msg?)), + ); *cnt += 1; - match msg { - ServerMsg::TooManyPlayers => { - return Err(Error::ServerWentMad); - }, - ServerMsg::Disconnect(reason) => match reason { - DisconnectReason::Shutdown => return Err(Error::ServerShutdown), - DisconnectReason::Requested => { - debug!("finally sending ClientMsg::Terminate"); - frontend_events.push(Event::Disconnect); - self.singleton_stream.send(ClientMsg::Terminate)?; - break Ok(()); - }, - DisconnectReason::Kicked(reason) => { - debug!("sending ClientMsg::Terminate because we got kicked"); - frontend_events.push(Event::Kicked(reason.clone())); - self.singleton_stream.send(ClientMsg::Terminate)?; - }, - }, - ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad), - ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => { - self.player_list = list - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { - if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) - { - warn!( - "Received msg to insert {} with uid {} into the player list but there \ - was already an entry for {} with the same uid that was overwritten!", - player_info.player_alias, uid, old_player_info.player_alias - ); - } - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { - if let Some(player_info) = self.player_list.get_mut(&uid) { - player_info.is_admin = admin; - } else { - warn!( - "Received msg to update admin status of uid {}, but they were not in \ - the list.", - uid - ); - } - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( - uid, - char_info, - )) => { - if let Some(player_info) = self.player_list.get_mut(&uid) { - player_info.character = Some(char_info); - } else { - warn!( - "Received msg to update character info for uid {}, but they were not \ - in the list.", - uid - ); - } - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { - if let Some(player_info) = self.player_list.get_mut(&uid) { - player_info.character = match &player_info.character { - Some(character) => Some(common::msg::CharacterInfo { - name: character.name.to_string(), - level: next_level, - }), - None => { - warn!( - "Received msg to update character level info to {} for uid \ - {}, but this player's character is None.", - next_level, uid - ); - - None - }, - }; - } - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { - // Instead of removing players, mark them as offline because we need to - // remember the names of disconnected players in chat. - // - // TODO the server should re-use uids of players that log out and log back - // in. - - if let Some(player_info) = self.player_list.get_mut(&uid) { - if player_info.is_online { - player_info.is_online = false; - } else { - warn!( - "Received msg to remove uid {} from the player list by they were \ - already marked offline", - uid - ); - } - } else { - warn!( - "Received msg to remove uid {} from the player list by they weren't \ - in the list!", - uid - ); - } - }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { - if let Some(player_info) = self.player_list.get_mut(&uid) { - player_info.player_alias = new_name; - } else { - warn!( - "Received msg to alias player with uid {} to {} but this uid is not \ - in the player list", - uid, new_name - ); - } - }, - ServerMsg::GroupUpdate(change_notification) => { - use comp::group::ChangeNotification::*; - // Note: we use a hashmap since this would not work with entities outside - // the view distance - match change_notification { - Added(uid, role) => { - // Check if this is a newly formed group by looking for absence of - // other non pet group members - if !matches!(role, group::Role::Pet) - && !self - .group_members - .values() - .any(|r| !matches!(r, group::Role::Pet)) - { - frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg( - "Type /g or /group to chat with your group members", - ))); - } - if let Some(player_info) = self.player_list.get(&uid) { - frontend_events.push(Event::Chat( - comp::ChatType::GroupMeta("Group".into()).chat_msg(format!( - "[{}] joined group", - self.personalize_alias( - uid, - player_info.player_alias.clone() - ) - )), - )); - } - if self.group_members.insert(uid, role) == Some(role) { - warn!( - "Received msg to add uid {} to the group members but they \ - were already there", - uid - ); - } - }, - Removed(uid) => { - if let Some(player_info) = self.player_list.get(&uid) { - frontend_events.push(Event::Chat( - comp::ChatType::GroupMeta("Group".into()).chat_msg(format!( - "[{}] left group", - self.personalize_alias( - uid, - player_info.player_alias.clone() - ) - )), - )); - } - if self.group_members.remove(&uid).is_none() { - warn!( - "Received msg to remove uid {} from group members but by they \ - weren't in there!", - uid - ); - } - }, - NewLeader(leader) => { - self.group_leader = Some(leader); - }, - NewGroup { leader, members } => { - self.group_leader = Some(leader); - self.group_members = members.into_iter().collect(); - // Currently add/remove messages treat client as an implicit member - // of the group whereas this message explicitly includes them so to - // be consistent for now we will remove the client from the - // received hashset - if let Some(uid) = self.uid() { - self.group_members.remove(&uid); - } - }, - NoGroup => { - self.group_leader = None; - self.group_members = HashMap::new(); - }, - } - }, - ServerMsg::GroupInvite { inviter, timeout } => { - self.group_invite = Some((inviter, std::time::Instant::now(), timeout)); - }, - ServerMsg::InvitePending(uid) => { - if !self.pending_invites.insert(uid) { - warn!("Received message about pending invite that was already pending"); - } - }, - ServerMsg::InviteComplete { target, answer } => { - if !self.pending_invites.remove(&target) { - warn!( - "Received completed invite message for invite that was not in the \ - list of pending invites" - ) - } - // TODO: expose this as a new event variant instead of going - // through the chat - let msg = match answer { - // TODO: say who accepted/declined/timed out the invite - InviteAnswer::Accepted => "Invite accepted", - InviteAnswer::Declined => "Invite declined", - InviteAnswer::TimedOut => "Invite timed out", - }; - frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg))); - }, - ServerMsg::Ping => { - self.singleton_stream.send(ClientMsg::Pong)?; - }, - ServerMsg::Pong => { - self.last_server_pong = self.state.get_time(); - self.last_ping_delta = self.state.get_time() - self.last_server_ping; - - // Maintain the correct number of deltas for calculating the rolling average - // ping. The client sends a ping to the server every second so we should be - // receiving a pong reply roughly every second. - while self.ping_deltas.len() > PING_ROLLING_AVERAGE_SECS - 1 { - self.ping_deltas.pop_front(); - } - self.ping_deltas.push_back(self.last_ping_delta); - }, - ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), - ServerMsg::SetPlayerEntity(uid) => { - if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { - self.entity = entity; - } else { - return Err(Error::Other("Failed to find entity from uid.".to_owned())); - } - }, - ServerMsg::TimeOfDay(time_of_day) => { - *self.state.ecs_mut().write_resource() = time_of_day; - }, - ServerMsg::EntitySync(entity_sync_package) => { - self.state - .ecs_mut() - .apply_entity_sync_package(entity_sync_package); - }, - ServerMsg::CompSync(comp_sync_package) => { - self.state - .ecs_mut() - .apply_comp_sync_package(comp_sync_package); - }, - ServerMsg::CreateEntity(entity_package) => { - self.state.ecs_mut().apply_entity_package(entity_package); - }, - ServerMsg::DeleteEntity(entity) => { - if self.uid() != Some(entity) { - self.state - .ecs_mut() - .delete_entity_and_clear_from_uid_allocator(entity.0); - } - }, - // Cleanup for when the client goes back to the `Registered` state - ServerMsg::ExitIngameCleanup => { - self.clean_state(); - }, - ServerMsg::InventoryUpdate(mut inventory, event) => { - match event { - InventoryUpdateEvent::CollectFailed => {}, - _ => { - inventory.recount_items(); - // Push the updated inventory component to the client - self.state.write_component(self.entity, inventory); - }, - } - - self.update_available_recipes(); - - frontend_events.push(Event::InventoryUpdated(event)); - }, - ServerMsg::TerrainChunkUpdate { key, chunk } => { - if let Ok(chunk) = chunk { - self.state.insert_chunk(key, *chunk); - } - self.pending_chunks.remove(&key); - }, - ServerMsg::TerrainBlockUpdates(mut blocks) => { - blocks.drain().for_each(|(pos, block)| { - self.state.set_block(pos, block); - }); - }, - ServerMsg::StateAnswer(Ok(state)) => { - self.client_state = state; - }, - ServerMsg::StateAnswer(Err((error, state))) => { - warn!( - "StateAnswer: {:?}. Server thinks client is in state {:?}.", - error, state - ); - }, - ServerMsg::CharacterListUpdate(character_list) => { - self.character_list.characters = character_list; - self.character_list.loading = false; - }, - ServerMsg::CharacterActionError(error) => { - warn!("CharacterActionError: {:?}.", error); - self.character_list.error = Some(error); - }, - ServerMsg::Notification(n) => { - frontend_events.push(Event::Notification(n)); - }, - ServerMsg::CharacterDataLoadError(error) => { - self.clean_state(); - self.character_list.error = Some(error); - }, - ServerMsg::SetViewDistance(vd) => { - self.view_distance = Some(vd); - frontend_events.push(Event::SetViewDistance(vd)); - }, - ServerMsg::Outcomes(outcomes) => { - frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) - }, - ServerMsg::Knockback(impulse) => { - self.state - .ecs() - .read_resource::>() - .emit_now(LocalEvent::ApplyImpulse { - entity: self.entity, - impulse, - }); - }, + if let Some(msg) = m1 { + self.handle_server_msg(frontend_events, msg)?; + } + if let Some(msg) = m2 { + self.handle_ping_msg(msg)?; + } + if let Some(msg) = m3 { + self.handle_server_not_in_game_msg(msg)?; + } + if let Some(msg) = m4 { + self.handle_server_in_game_msg(frontend_events, msg)?; } } } @@ -1484,7 +1497,7 @@ impl Client { //TIMEOUT 0.01 ms for msg handling select!( _ = Delay::new(std::time::Duration::from_micros(10)).fuse() => Ok(()), - err = self.handle_message(&mut frontend_events, &mut handles_msg).fuse() => err, + err = self.handle_messages(&mut frontend_events, &mut handles_msg).fuse() => err, ) })?; @@ -1503,8 +1516,11 @@ impl Client { /// Get the player's Uid. pub fn uid(&self) -> Option { self.state.read_component_copied(self.entity) } - /// Get the client state - pub fn get_client_state(&self) -> ClientState { self.client_state } + pub fn get_client_type(&self) -> ClientType { ClientType::Game } + + pub fn get_in_game(&self) -> Option { self.client_ingame } + + pub fn get_registered(&self) -> bool { self.registered } /// Get the current tick number. pub fn get_tick(&self) -> u64 { self.tick } diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index b39e21316b..5e464fc5b9 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -7,13 +7,26 @@ use crate::{ use serde::{Deserialize, Serialize}; use vek::*; -/// Messages sent from the client to the server -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClientMsg { - Register { - view_distance: Option, - token_or_username: String, - }, +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum ClientType { + // Regular Client like Voxygen who plays the game + Game, + // A Chatonly client, which doesn't want to connect via its character + ChatOnly, + // A unprivileged bot, e.g. to request world information + // Or a privileged bot, e.g. to run admin commands used by server-cli + Bot { privileged: bool }, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ClientRegisterMsg { + pub view_distance: Option, + pub token_or_username: String, +} + +//messages send by clients only valid when NOT ingame +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ClientNotInGameMsg { RequestCharacterList, CreateCharacter { alias: String, @@ -22,20 +35,19 @@ pub enum ClientMsg { }, DeleteCharacter(CharacterId), Character(CharacterId), - /// Request `ClientState::Registered` from an ingame state - ExitIngame, - /// Request `ClientState::Spectator` from a registered or ingame state Spectate, +} + +//messages send by clients only valid when ingame +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ClientInGameMsg { ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), ControlAction(comp::ControlAction), SetViewDistance(u32), BreakBlock(Vec3), PlaceBlock(Vec3, Block), - Ping, - Pong, - /// Send the chat message or command to be processed by the server - ChatMsg(String), + ExitInGame, PlayerPhysics { pos: comp::Pos, vel: comp::Vel, @@ -44,9 +56,16 @@ pub enum ClientMsg { TerrainChunkRequest { key: Vec2, }, - Disconnect, - Terminate, UnlockSkill(Skill), RefundSkill(Skill), UnlockSkillGroup(SkillGroupType), } + +/// Messages sent from the client to the server +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ClientMsg { + ChatMsg(String), + Command(String), + Disconnect, + Terminate, +} diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 64a6d4be1f..964ee157fc 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -4,24 +4,28 @@ pub mod server; // Reexports pub use self::{ - client::ClientMsg, + client::{ClientInGameMsg, ClientMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType}, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, RequestStateError, ServerInfo, ServerMsg, + RegisterError, ServerInGameMsg, ServerInfo, ServerInitMsg, ServerMsg, ServerNotInGameMsg, + ServerRegisterAnswerMsg, }, }; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ClientState { - Pending, - Connected, - Registered, +pub enum ClientIngame { Spectator, Character, } +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum PingMsg { + Ping, + Pong, +} + pub const MAX_BYTES_CHAT_MSG: usize = 256; pub enum ChatMsgValidationError { diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index ce86e46c73..8aaa9873c4 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,4 +1,4 @@ -use super::{ClientState, EcsCompPacket}; +use super::EcsCompPacket; use crate::{ character::CharacterItem, comp, @@ -192,25 +192,37 @@ pub enum DisconnectReason { Kicked(String), } -/// Messages sent from the server to the client +/// Reponse To ClientType #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerMsg { - InitialSync { +pub enum ServerInitMsg { + TooManyPlayers, + GameSync { entity_package: sync::EntityPackage, - server_info: ServerInfo, time_of_day: state::TimeOfDay, max_group_size: u32, client_timeout: Duration, world_map: WorldMapMsg, recipe_book: RecipeBook, }, +} + +pub type ServerRegisterAnswerMsg = Result<(), RegisterError>; + +//Messages only allowed while client ingame +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerNotInGameMsg { /// An error occurred while loading character data CharacterDataLoadError(String), /// A list of characters belonging to the a authenticated player was sent CharacterListUpdate(Vec), /// An error occurred while creating or deleting a character CharacterActionError(String), - PlayerListUpdate(PlayerListUpdate), + CharacterSuccess, +} + +//Messages only allowed while client ingame +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerInGameMsg { GroupUpdate(comp::group::ChangeNotification), // Indicate to the client that they are invited to join a group GroupInvite { @@ -227,12 +239,24 @@ pub enum ServerMsg { target: sync::Uid, answer: InviteAnswer, }, - StateAnswer(Result), /// Trigger cleanup for when the client goes back to the `Registered` state /// from an ingame state - ExitIngameCleanup, - Ping, - Pong, + ExitInGameSuccess, + InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent), + TerrainChunkUpdate { + key: Vec2, + chunk: Result, ()>, + }, + TerrainBlockUpdates(HashMap, Block>), + SetViewDistance(u32), + Outcomes(Vec), + Knockback(Vec3), +} + +/// Messages sent from the server to the client +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerMsg { + PlayerListUpdate(PlayerListUpdate), /// A message to go into the client chat box. The client is responsible for /// formatting the message and turning it into a speech bubble. ChatMsg(comp::ChatMsg), @@ -242,28 +266,9 @@ pub enum ServerMsg { CompSync(sync::CompSyncPackage), CreateEntity(sync::EntityPackage), DeleteEntity(Uid), - InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent), - TerrainChunkUpdate { - key: Vec2, - chunk: Result, ()>, - }, - TerrainBlockUpdates(HashMap, Block>), Disconnect(DisconnectReason), - TooManyPlayers, /// Send a popup notification such as "Waypoint Saved" Notification(Notification), - SetViewDistance(u32), - Outcomes(Vec), - Knockback(Vec3), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum RequestStateError { - RegisterDenied(RegisterError), - Denied, - Already, - Impossible, - WrongMessage, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/server/src/client.rs b/server/src/client.rs index 3cb76f659b..7423589d1f 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,20 +1,30 @@ use crate::error::Error; -use common::msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}; +use common::msg::{ + ClientInGameMsg, ClientIngame, ClientMsg, ClientNotInGameMsg, ClientType, PingMsg, + ServerInGameMsg, ServerInitMsg, ServerMsg, ServerNotInGameMsg, +}; use hashbrown::HashSet; -use network::{Participant, Stream}; +use network::{MessageBuffer, Participant, Stream}; +use serde::{de::DeserializeOwned, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; use std::sync::{ atomic::{AtomicBool, Ordering}, - Mutex, + Arc, Mutex, }; use tracing::debug; use vek::*; pub struct Client { - pub client_state: ClientState, + pub registered: bool, + pub client_type: ClientType, + pub in_game: Option, pub participant: Mutex>, pub singleton_stream: Stream, + pub ping_stream: Stream, + pub register_stream: Stream, + pub in_game_stream: Stream, + pub not_in_game_stream: Stream, pub network_error: AtomicBool, pub last_ping: f64, pub login_msg_sent: bool, @@ -25,22 +35,58 @@ impl Component for Client { } impl Client { - pub fn notify(&mut self, msg: ServerMsg) { - if !self.network_error.load(Ordering::Relaxed) { - if let Err(e) = self.singleton_stream.send(msg) { + fn internal_send(b: &AtomicBool, s: &mut Stream, msg: M) { + if !b.load(Ordering::Relaxed) { + if let Err(e) = s.send(msg) { debug!(?e, "got a network error with client"); - self.network_error.store(true, Ordering::Relaxed); + b.store(true, Ordering::Relaxed); } } } - pub async fn recv(&mut self) -> Result { - if !self.network_error.load(Ordering::Relaxed) { - match self.singleton_stream.recv().await { + fn internal_send_raw(b: &AtomicBool, s: &mut Stream, msg: Arc) { + if !b.load(Ordering::Relaxed) { + if let Err(e) = s.send_raw(msg) { + debug!(?e, "got a network error with client"); + b.store(true, Ordering::Relaxed); + } + } + } + + pub fn send_init(&mut self, msg: ServerInitMsg) { + Self::internal_send(&self.network_error, &mut self.register_stream, msg); + } + + pub fn send_msg(&mut self, msg: ServerMsg) { + Self::internal_send(&self.network_error, &mut self.singleton_stream, msg); + } + + pub fn send_in_game(&mut self, msg: ServerInGameMsg) { + Self::internal_send(&self.network_error, &mut self.in_game_stream, msg); + } + + pub fn send_not_in_game(&mut self, msg: ServerNotInGameMsg) { + Self::internal_send(&self.network_error, &mut self.not_in_game_stream, msg); + } + + pub fn send_ping(&mut self, msg: PingMsg) { + Self::internal_send(&self.network_error, &mut self.ping_stream, msg); + } + + pub fn send_msg_raw(&mut self, msg: Arc) { + Self::internal_send_raw(&self.network_error, &mut self.singleton_stream, msg); + } + + pub async fn internal_recv( + b: &AtomicBool, + s: &mut Stream, + ) -> Result { + if !b.load(Ordering::Relaxed) { + match s.recv().await { Ok(r) => Ok(r), Err(e) => { debug!(?e, "got a network error with client while recv"); - self.network_error.store(true, Ordering::Relaxed); + b.store(true, Ordering::Relaxed); Err(Error::StreamErr(e)) }, } @@ -49,29 +95,20 @@ impl Client { } } - pub fn is_registered(&self) -> bool { - matches!( - self.client_state, - ClientState::Registered | ClientState::Spectator | ClientState::Character - ) + pub async fn recv_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.singleton_stream).await } - pub fn is_ingame(&self) -> bool { - matches!( - self.client_state, - ClientState::Spectator | ClientState::Character - ) + pub async fn recv_in_game_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.in_game_stream).await } - pub fn allow_state(&mut self, new_state: ClientState) { - self.client_state = new_state; - let _ = self - .singleton_stream - .send(ServerMsg::StateAnswer(Ok(new_state))); + pub async fn recv_not_in_game_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.not_in_game_stream).await } - pub fn error_state(&mut self, error: RequestStateError) { - let _ = self.notify(ServerMsg::StateAnswer(Err((error, self.client_state)))); + pub async fn recv_ping_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.ping_stream).await } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 704b290d9e..ccd9ca219f 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerMsg}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerInGameMsg, ServerMsg}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -667,7 +667,9 @@ fn handle_spawn( .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| { + c.send_in_game(ServerInGameMsg::GroupUpdate(g)) + }); }, ); } else if let Some(group) = match alignment { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 308363bc22..edb13243eb 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerMsg}, + msg::{PlayerListUpdate, ServerInGameMsg, ServerMsg}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3) } let mut clients = state.ecs().write_storage::(); if let Some(client) = clients.get_mut(entity) { - client.notify(ServerMsg::Knockback(impulse)); + client.send_in_game(ServerInGameMsg::Knockback(impulse)); } } diff --git a/server/src/events/group_manip.rs b/server/src/events/group_manip.rs index fb028729de..6c96644968 100644 --- a/server/src/events/group_manip.rs +++ b/server/src/events/group_manip.rs @@ -5,7 +5,7 @@ use common::{ group::{self, Group, GroupManager, Invite, PendingInvites}, ChatType, GroupManip, }, - msg::{InviteAnswer, ServerMsg}, + msg::{InviteAnswer, ServerInGameMsg}, sync, sync::WorldSyncExt, }; @@ -31,7 +31,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani None => { // Inform of failure if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("Invite failed, target does not exist.".to_owned()), ); @@ -63,7 +63,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if already_in_same_group { // Inform of failure if let Some(client) = clients.get_mut(entity) { - client.notify(ChatType::Meta.server_msg( + client.send_msg(ChatType::Meta.server_msg( "Invite failed, can't invite someone already in your group".to_owned(), )); } @@ -93,7 +93,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if group_size_limit_reached { // Inform inviter that they have reached the group size limit if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta.server_msg( "Invite failed, pending invites plus current group size have reached \ the group size limit" @@ -110,7 +110,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if invites.contains(invitee) { // Inform inviter that there is already an invite if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("This player already has a pending invite.".to_owned()), ); @@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani (clients.get_mut(invitee), uids.get(entity).copied()) { if send_invite() { - client.notify(ServerMsg::GroupInvite { + client.send_in_game(ServerInGameMsg::GroupInvite { inviter, timeout: PRESENTED_INVITE_TIMEOUT_DUR, }); @@ -163,7 +163,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani } else if agents.contains(invitee) { send_invite(); } else if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta.server_msg("Can't invite, not a player or npc".to_owned()), ); } @@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani // Notify inviter that the invite is pending if invite_sent { if let Some(client) = clients.get_mut(entity) { - client.notify(ServerMsg::InvitePending(uid)); + client.send_in_game(ServerInGameMsg::InvitePending(uid)); } } }, @@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.notify(ServerMsg::InviteComplete { + client.send_in_game(ServerInGameMsg::InviteComplete { target, answer: InviteAnswer::Accepted, }) @@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); }, ); } @@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.notify(ServerMsg::InviteComplete { + client.send_in_game(ServerInGameMsg::InviteComplete { target, answer: InviteAnswer::Declined, }) @@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); }, ); }, @@ -283,7 +283,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani None => { // Inform of failure if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("Kick failed, target does not exist.".to_owned()), ); @@ -296,7 +296,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner)) { if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta.server_msg("Kick failed, you can't kick pets.".to_owned()), ); } @@ -305,7 +305,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani // Can't kick yourself if uids.get(entity).map_or(false, |u| *u == uid) { if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("Kick failed, you can't kick yourself.".to_owned()), ); @@ -336,26 +336,26 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); }, ); // Tell them the have been kicked if let Some(client) = clients.get_mut(target) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("You were removed from the group.".to_owned()), ); } // Tell kicker that they were succesful if let Some(client) = clients.get_mut(entity) { - client.notify(ChatType::Meta.server_msg("Player kicked.".to_owned())); + client.send_msg(ChatType::Meta.server_msg("Player kicked.".to_owned())); } }, Some(_) => { // Inform kicker that they are not the leader if let Some(client) = clients.get_mut(entity) { - client.notify(ChatType::Meta.server_msg( + client.send_msg(ChatType::Meta.server_msg( "Kick failed: You are not the leader of the target's group.".to_owned(), )); } @@ -363,7 +363,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani None => { // Inform kicker that the target is not in a group if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta.server_msg( "Kick failed: Your target is not in a group.".to_owned(), ), @@ -380,7 +380,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani None => { // Inform of failure if let Some(client) = clients.get_mut(entity) { - client.notify(ChatType::Meta.server_msg( + client.send_msg(ChatType::Meta.server_msg( "Leadership transfer failed, target does not exist".to_owned(), )); } @@ -410,18 +410,18 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); }, ); // Tell them they are the leader if let Some(client) = clients.get_mut(target) { - client.notify( + client.send_msg( ChatType::Meta.server_msg("You are the group leader now.".to_owned()), ); } // Tell the old leader that the transfer was succesful if let Some(client) = clients.get_mut(target) { - client.notify( + client.send_msg( ChatType::Meta .server_msg("You are no longer the group leader.".to_owned()), ); @@ -430,7 +430,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani Some(_) => { // Inform transferer that they are not the leader if let Some(client) = clients.get_mut(entity) { - client.notify( + client.send_msg( ChatType::Meta.server_msg( "Transfer failed: You are not the leader of the target's group." .to_owned(), @@ -441,7 +441,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani None => { // Inform transferer that the target is not in a group if let Some(client) = clients.get_mut(entity) { - client.notify(ChatType::Meta.server_msg( + client.send_msg(ChatType::Meta.server_msg( "Transfer failed: Your target is not in a group.".to_owned(), )); } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index b5060208f5..e34ea80d52 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { let mut clients = ecs.write_storage::(); if clients.get_mut(possesse).is_none() { if let Some(mut client) = clients.remove(possessor) { - client.notify(ServerMsg::SetPlayerEntity(possesse_uid)); + client.send_msg(ServerMsg::SetPlayerEntity(possesse_uid)); clients .insert(possesse, client) .err() diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 1f879bcbc4..c894479dc8 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -5,7 +5,7 @@ use common::{ slot::{self, Slot}, Pos, MAX_PICKUP_RANGE_SQR, }, - msg::ServerMsg, + msg::ServerInGameMsg, recipe::default_recipe_book, sync::{Uid, WorldSyncExt}, vol::ReadVol, @@ -281,7 +281,9 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .map(|g| (g, c)) }) .map(|(g, c)| { - c.notify(ServerMsg::GroupUpdate(g)) + c.send_in_game( + ServerInGameMsg::GroupUpdate(g), + ) }); }, ); diff --git a/server/src/events/player.rs b/server/src/events/player.rs index f92167bda8..910e932131 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{ClientState, PlayerListUpdate, ServerMsg}, + msg::{PlayerListUpdate, ServerInGameMsg, ServerMsg}, span, sync::{Uid, UidAllocator}, }; @@ -33,9 +33,8 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { .cloned(); if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { // Tell client its request was successful - client.allow_state(ClientState::Registered); - // Tell client to clear out other entities and its own components - client.notify(ServerMsg::ExitIngameCleanup); + client.in_game = None; + client.send_in_game(ServerInGameMsg::ExitInGameSuccess); let entity_builder = state.ecs_mut().create_entity().with(client).with(player); diff --git a/server/src/lib.rs b/server/src/lib.rs index 2cb6b1e1d9..c0b0e25526 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -44,7 +44,10 @@ use common::{ cmd::ChatCommand, comp::{self, ChatType}, event::{EventBus, ServerEvent}, - msg::{server::WorldMapMsg, ClientState, DisconnectReason, ServerInfo, ServerMsg}, + msg::{ + server::WorldMapMsg, ClientType, DisconnectReason, ServerInGameMsg, ServerInfo, + ServerInitMsg, ServerMsg, ServerNotInGameMsg, + }, outcome::Outcome, recipe::default_recipe_book, state::{State, TimeOfDay}, @@ -56,7 +59,7 @@ use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; use metrics::{ServerMetrics, StateTickMetrics, TickMetrics}; -use network::{Network, Pid, ProtocolAddr}; +use network::{Network, Pid, Promises, ProtocolAddr}; use persistence::{ character_loader::{CharacterLoader, CharacterLoaderResponseType}, character_updater::CharacterUpdater, @@ -70,7 +73,7 @@ use std::{ }; #[cfg(not(feature = "worldgen"))] use test_world::{IndexOwned, World}; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, trace}; use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; #[cfg(feature = "worldgen")] @@ -521,13 +524,13 @@ impl Server { .messages() .for_each(|query_result| match query_result.result { CharacterLoaderResponseType::CharacterList(result) => match result { - Ok(character_list_data) => self.notify_client( + Ok(character_list_data) => self.notify_not_in_game_client( query_result.entity, - ServerMsg::CharacterListUpdate(character_list_data), + ServerNotInGameMsg::CharacterListUpdate(character_list_data), ), - Err(error) => self.notify_client( + Err(error) => self.notify_not_in_game_client( query_result.entity, - ServerMsg::CharacterActionError(error.to_string()), + ServerNotInGameMsg::CharacterActionError(error.to_string()), ), }, CharacterLoaderResponseType::CharacterData(result) => { @@ -540,9 +543,9 @@ impl Server { // We failed to load data for the character from the DB. Notify the // client to push the state back to character selection, with the error // to display - self.notify_client( + self.notify_not_in_game_client( query_result.entity, - ServerMsg::CharacterDataLoadError(error.to_string()), + ServerNotInGameMsg::CharacterDataLoadError(error.to_string()), ); // Clean up the entity data on the server @@ -797,7 +800,6 @@ impl Server { ) -> Result<(), Error> { //TIMEOUT 0.1 ms for msg handling const TIMEOUT: Duration = Duration::from_micros(100); - const SLOWLORIS_TIMEOUT: Duration = Duration::from_millis(300); loop { let participant = match select!( _ = Delay::new(TIMEOUT).fuse() => None, @@ -807,71 +809,77 @@ impl Server { Some(pr) => pr?, }; debug!("New Participant connected to the server"); + let reliable = Promises::ORDERED | Promises::CONSISTENCY; + let reliablec = reliable | Promises::COMPRESSED; - let singleton_stream = match select!( - _ = Delay::new(SLOWLORIS_TIMEOUT).fuse() => None, - sr = participant.opened().fuse() => Some(sr), - ) { - None => { - warn!("Either Slowloris attack or very slow client, dropping"); - return Ok(()); //return rather then continue to give removes a tick more to send data. - }, - Some(Ok(s)) => s, - Some(Err(e)) => { - warn!(?e, "Failed to open a Stream from remote client. dropping"); - continue; - }, - }; + let stream = participant.open(10, reliablec).await?; + let ping_stream = participant.open(5, reliable).await?; + let mut register_stream = participant.open(10, reliablec).await?; + let in_game_stream = participant.open(10, reliablec).await?; + let not_in_game_stream = participant.open(10, reliablec).await?; - let mut client = Client { - client_state: ClientState::Connected, + register_stream.send(self.get_server_info())?; + let client_type: ClientType = register_stream.recv().await?; + + if self.settings().max_players + <= self.state.ecs().read_storage::().join().count() + { + trace!( + ?participant, + "to many players, wont allow participant to connect" + ); + register_stream.send(ServerInitMsg::TooManyPlayers)?; + continue; + } + + let client = Client { + registered: false, + client_type, + in_game: None, participant: std::sync::Mutex::new(Some(participant)), - singleton_stream, + singleton_stream: stream, + ping_stream, + register_stream, + in_game_stream, + not_in_game_stream, network_error: std::sync::atomic::AtomicBool::new(false), last_ping: self.state.get_time(), login_msg_sent: false, }; - if self.settings().max_players - <= self.state.ecs().read_storage::().join().count() - { - // Note: in this case the client is dropped - client.notify(ServerMsg::TooManyPlayers); - } else { - let entity = self - .state - .ecs_mut() - .create_entity_synced() - .with(client) - .build(); - self.state - .ecs() - .read_resource::() - .clients_connected - .inc(); - // Send client all the tracked components currently attached to its entity as - // well as synced resources (currently only `TimeOfDay`) - debug!("Starting initial sync with client."); - self.state - .ecs() - .write_storage::() - .get_mut(entity) - .unwrap() - .notify(ServerMsg::InitialSync { - // Send client their entity - entity_package: TrackedComps::fetch(&self.state.ecs()) - .create_entity_package(entity, None, None, None), - server_info: self.get_server_info(), - time_of_day: *self.state.ecs().read_resource(), - max_group_size: self.settings().max_player_group_size, - client_timeout: self.settings().client_timeout, - world_map: self.map.clone(), - recipe_book: (&*default_recipe_book()).clone(), - }); + let entity = self + .state + .ecs_mut() + .create_entity_synced() + .with(client) + .build(); + self.state + .ecs() + .read_resource::() + .clients_connected + .inc(); + // Send client all the tracked components currently attached to its entity as + // well as synced resources (currently only `TimeOfDay`) + debug!("Starting initial sync with client."); + self.state + .ecs() + .write_storage::() + .get_mut(entity) + .unwrap() + .register_stream + .send(ServerInitMsg::GameSync { + // Send client their entity + entity_package: TrackedComps::fetch(&self.state.ecs()) + .create_entity_package(entity, None, None, None), + time_of_day: *self.state.ecs().read_resource(), + max_group_size: self.settings().max_player_group_size, + client_timeout: self.settings().client_timeout, + world_map: self.map.clone(), + recipe_book: (&*default_recipe_book()).clone(), + })?; - frontend_events.push(Event::ClientConnected { entity }); - debug!("Done initial sync with client."); - } + frontend_events.push(Event::ClientConnected { entity }); + debug!("Done initial sync with client."); } } @@ -880,7 +888,25 @@ impl Server { S: Into, { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.notify(msg.into()) + client.send_msg(msg.into()) + } + } + + pub fn notify_in_game_client(&self, entity: EcsEntity, msg: S) + where + S: Into, + { + if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { + client.send_in_game(msg.into()) + } + } + + pub fn notify_not_in_game_client(&self, entity: EcsEntity, msg: S) + where + S: Into, + { + if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { + client.send_not_in_game(msg.into()) } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index e51de63761..14ef5bbf70 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -5,7 +5,10 @@ use common::{ character::CharacterId, comp, effect::Effect, - msg::{CharacterInfo, ClientState, PlayerListUpdate, ServerMsg}, + msg::{ + CharacterInfo, ClientIngame, PlayerListUpdate, ServerInGameMsg, ServerMsg, + ServerNotInGameMsg, + }, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, util::Dir, @@ -216,7 +219,8 @@ impl StateExt for State { // Tell the client its request was successful. if let Some(client) = self.ecs().write_storage::().get_mut(entity) { - client.allow_state(ClientState::Character); + client.in_game = Some(ClientIngame::Character); + client.send_not_in_game(ServerNotInGameMsg::CharacterSuccess) } } @@ -282,7 +286,7 @@ impl StateExt for State { .join() { if uid == u || uid == t { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -294,7 +298,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -306,7 +310,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -318,7 +322,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -332,7 +336,7 @@ impl StateExt for State { .join() { if s == &faction.0 { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -344,7 +348,7 @@ impl StateExt for State { .join() { if g == group { - client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -355,9 +359,9 @@ impl StateExt for State { fn notify_registered_clients(&self, msg: ServerMsg) { for client in (&mut self.ecs().write_storage::()) .join() - .filter(|c| c.is_registered()) + .filter(|c| c.registered) { - client.notify(msg.clone()); + client.send_msg(msg.clone()); } } @@ -384,7 +388,7 @@ impl StateExt for State { .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); }, ); } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 1dfd92e4aa..3bd9b1d415 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::ServerMsg, + msg::{ServerInGameMsg, ServerMsg}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -107,7 +107,7 @@ impl<'a> System<'a> for Sys { let mut subscribers = (&mut clients, &entities, &subscriptions, &positions) .join() .filter_map(|(client, entity, subscription, pos)| { - if client.is_ingame() && subscription.regions.contains(&key) { + if client.in_game.is_some() && subscription.regions.contains(&key) { Some((client, &subscription.regions, entity, *pos)) } else { None @@ -143,7 +143,7 @@ impl<'a> System<'a> for Sys { // Client doesn't need to know about itself && *client_entity != entity { - client.notify(create_msg.clone()); + client.send_msg(create_msg.clone()); } } } @@ -157,7 +157,7 @@ impl<'a> System<'a> for Sys { .map(|key| !regions.contains(key)) .unwrap_or(true) { - client.notify(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerMsg::DeleteEntity(uid)); } } } @@ -177,8 +177,8 @@ impl<'a> System<'a> for Sys { let entity_sync_msg = ServerMsg::EntitySync(entity_sync_package); let comp_sync_msg = ServerMsg::CompSync(comp_sync_package); subscribers.iter_mut().for_each(move |(client, _, _, _)| { - client.notify(entity_sync_msg.clone()); - client.notify(comp_sync_msg.clone()); + client.send_msg(entity_sync_msg.clone()); + client.send_msg(comp_sync_msg.clone()); }); let mut send_msg = |msg: ServerMsg, @@ -212,7 +212,7 @@ impl<'a> System<'a> for Sys { true // Closer than 100 blocks } } { - client.notify(msg.clone()); + client.send_msg(msg.clone()); } } }; @@ -303,7 +303,7 @@ impl<'a> System<'a> for Sys { (&mut clients, &subscriptions) .join() .filter_map(|(client, subscription)| { - if client.is_ingame() && subscription.regions.contains(®ion_key) { + if client.in_game.is_some() && subscription.regions.contains(®ion_key) { Some(client) } else { None @@ -311,7 +311,7 @@ impl<'a> System<'a> for Sys { }) { for uid in &deleted { - client.notify(ServerMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerMsg::DeleteEntity(Uid(*uid))); } } } @@ -320,7 +320,7 @@ impl<'a> System<'a> for Sys { // Sync inventories for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() { - client.notify(ServerMsg::InventoryUpdate( + client.send_in_game(ServerInGameMsg::InventoryUpdate( inventory.clone(), update.event(), )); @@ -341,7 +341,7 @@ impl<'a> System<'a> for Sys { .cloned() .collect::>(); if !outcomes.is_empty() { - client.notify(ServerMsg::Outcomes(outcomes)); + client.send_in_game(ServerInGameMsg::Outcomes(outcomes)); } } outcomes.clear(); @@ -355,7 +355,7 @@ impl<'a> System<'a> for Sys { // system?) let tof_msg = ServerMsg::TimeOfDay(*time_of_day); for client in (&mut clients).join() { - client.notify(tof_msg.clone()); + client.send_msg(tof_msg.clone()); } timer.end(); diff --git a/server/src/sys/invite_timeout.rs b/server/src/sys/invite_timeout.rs index 834f812a84..2afd405c11 100644 --- a/server/src/sys/invite_timeout.rs +++ b/server/src/sys/invite_timeout.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::group::{Invite, PendingInvites}, - msg::{InviteAnswer, ServerMsg}, + msg::{InviteAnswer, ServerInGameMsg}, span, sync::Uid, }; @@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys { if let (Some(client), Some(target)) = (clients.get_mut(*inviter), uids.get(invitee).copied()) { - client.notify(ServerMsg::InviteComplete { + client.send_in_game(ServerInGameMsg::InviteComplete { target, answer: InviteAnswer::TimedOut, }) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index ed6eb8ee4a..8cbbb947be 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,9 +15,10 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientMsg, ClientState, - DisconnectReason, PlayerInfo, PlayerListUpdate, RequestStateError, ServerMsg, - MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientInGameMsg, ClientIngame, + ClientMsg, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, PingMsg, PlayerInfo, + PlayerListUpdate, RegisterError, ServerInGameMsg, ServerMsg, ServerNotInGameMsg, + ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -32,13 +33,435 @@ use hashbrown::HashMap; use specs::{ Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage, }; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, trace, warn}; impl Sys { + #[allow(clippy::too_many_arguments)] + fn handle_client_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, + entity: specs::Entity, + client: &mut Client, + player_metrics: &ReadExpect<'_, PlayerMetrics>, + uids: &ReadStorage<'_, Uid>, + chat_modes: &ReadStorage<'_, ChatMode>, + msg: ClientMsg, + ) -> Result<(), crate::error::Error> { + match msg { + ClientMsg::ChatMsg(message) => { + if client.registered { + match validate_chat_msg(&message) { + Ok(()) => { + if let Some(from) = uids.get(entity) { + let mode = chat_modes.get(entity).cloned().unwrap_or_default(); + let msg = mode.new_message(*from, message); + new_chat_msgs.push((Some(entity), msg)); + } else { + error!("Could not send message. Missing player uid"); + } + }, + Err(ChatMsgValidationError::TooLong) => { + let max = MAX_BYTES_CHAT_MSG; + let len = message.len(); + warn!(?len, ?max, "Received a chat message that's too long") + }, + } + } + }, + ClientMsg::Command(message) => { + if client.registered { + match validate_chat_msg(&message) { + Ok(()) => { + if let Some(from) = uids.get(entity) { + let mode = chat_modes.get(entity).cloned().unwrap_or_default(); + let msg = mode.new_message(*from, message); + new_chat_msgs.push((Some(entity), msg)); + } else { + error!("Could not send message. Missing player uid"); + } + }, + Err(ChatMsgValidationError::TooLong) => { + let max = MAX_BYTES_CHAT_MSG; + let len = message.len(); + warn!(?len, ?max, "Received a chat message that's too long") + }, + } + } + }, + ClientMsg::Disconnect => { + client.send_msg(ServerMsg::Disconnect(DisconnectReason::Requested)); + }, + ClientMsg::Terminate => { + debug!(?entity, "Client send message to termitate session"); + player_metrics + .clients_disconnected + .with_label_values(&["gracefully"]) + .inc(); + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); + }, + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_client_in_game_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + entity: specs::Entity, + client: &mut Client, + terrain: &ReadExpect<'_, TerrainGrid>, + network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, + can_build: &ReadStorage<'_, CanBuild>, + force_updates: &ReadStorage<'_, ForceUpdate>, + stats: &mut WriteStorage<'_, Stats>, + block_changes: &mut Write<'_, BlockChange>, + positions: &mut WriteStorage<'_, Pos>, + velocities: &mut WriteStorage<'_, Vel>, + orientations: &mut WriteStorage<'_, Ori>, + players: &mut WriteStorage<'_, Player>, + controllers: &mut WriteStorage<'_, Controller>, + settings: &Read<'_, Settings>, + msg: ClientInGameMsg, + ) -> Result<(), crate::error::Error> { + if !client.in_game.is_some() { + debug!(?entity, "client is not in_game, ignoring msg"); + trace!(?msg, "ignored msg content"); + if matches!(msg, ClientInGameMsg::TerrainChunkRequest{ .. }) { + network_metrics.chunks_request_dropped.inc(); + } + return Ok(()); + } + match msg { + // Go back to registered state (char selection screen) + ClientInGameMsg::ExitInGame => { + client.in_game = None; + server_emitter.emit(ServerEvent::ExitIngame { entity }); + client.send_in_game(ServerInGameMsg::ExitInGameSuccess); + }, + ClientInGameMsg::SetViewDistance(view_distance) => { + players.get_mut(entity).map(|player| { + player.view_distance = Some( + settings + .max_view_distance + .map(|max| view_distance.min(max)) + .unwrap_or(view_distance), + ) + }); + + if settings + .max_view_distance + .map(|max| view_distance > max) + .unwrap_or(false) + { + client.send_in_game(ServerInGameMsg::SetViewDistance( + settings.max_view_distance.unwrap_or(0), + )); + } + }, + ClientInGameMsg::ControllerInputs(inputs) => { + if let Some(ClientIngame::Character) = client.in_game { + if let Some(controller) = controllers.get_mut(entity) { + controller.inputs.update_with_new(inputs); + } + } + }, + ClientInGameMsg::ControlEvent(event) => { + if let Some(ClientIngame::Character) = client.in_game { + // Skip respawn if client entity is alive + if let ControlEvent::Respawn = event { + if stats.get(entity).map_or(true, |s| !s.is_dead) { + //Todo: comment why return! + return Ok(()); + } + } + if let Some(controller) = controllers.get_mut(entity) { + controller.events.push(event); + } + } + }, + ClientInGameMsg::ControlAction(event) => { + if let Some(ClientIngame::Character) = client.in_game { + if let Some(controller) = controllers.get_mut(entity) { + controller.actions.push(event); + } + } + }, + ClientInGameMsg::PlayerPhysics { pos, vel, ori } => { + if let Some(ClientIngame::Character) = client.in_game { + if force_updates.get(entity).is_none() + && stats.get(entity).map_or(true, |s| !s.is_dead) + { + let _ = positions.insert(entity, pos); + let _ = velocities.insert(entity, vel); + let _ = orientations.insert(entity, ori); + } + } + }, + ClientInGameMsg::BreakBlock(pos) => { + if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { + block_changes.set(pos, block.into_vacant()); + } + }, + ClientInGameMsg::PlaceBlock(pos, block) => { + if can_build.get(entity).is_some() { + block_changes.try_set(pos, block); + } + }, + ClientInGameMsg::TerrainChunkRequest { key } => { + let in_vd = if let (Some(view_distance), Some(pos)) = ( + players.get(entity).and_then(|p| p.view_distance), + positions.get(entity), + ) { + pos.0.xy().map(|e| e as f64).distance( + key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), + ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) + * TerrainChunkSize::RECT_SIZE.x as f64 + } else { + true + }; + if in_vd { + match terrain.get_key(key) { + Some(chunk) => { + network_metrics.chunks_served_from_memory.inc(); + client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + key, + chunk: Ok(Box::new(chunk.clone())), + }) + }, + None => { + network_metrics.chunks_generation_triggered.inc(); + server_emitter.emit(ServerEvent::ChunkRequest(entity, key)) + }, + } + } else { + network_metrics.chunks_request_dropped.inc(); + } + }, + ClientInGameMsg::UnlockSkill(skill) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.unlock_skill(skill)); + }, + ClientInGameMsg::RefundSkill(skill) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.refund_skill(skill)); + }, + ClientInGameMsg::UnlockSkillGroup(skill_group_type) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); + }, + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_client_not_in_game_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, + entity: specs::Entity, + client: &mut Client, + character_loader: &ReadExpect<'_, CharacterLoader>, + uids: &ReadStorage<'_, Uid>, + players: &mut WriteStorage<'_, Player>, + editable_settings: &ReadExpect<'_, EditableSettings>, + alias_validator: &ReadExpect<'_, AliasValidator>, + msg: ClientNotInGameMsg, + ) -> Result<(), crate::error::Error> { + match msg { + // Request spectator state + ClientNotInGameMsg::Spectate => { + if client.registered { + client.in_game = Some(ClientIngame::Spectator) + } else { + debug!("dropped Spectate msg from unregistered client"); + } + }, + ClientNotInGameMsg::Character(character_id) => { + if client.registered && !client.in_game.is_some() { + // Only send login message if it wasn't already + // sent previously + if let Some(player) = players.get(entity) { + // Send a request to load the character's component data from the + // DB. Once loaded, persisted components such as stats and inventory + // will be inserted for the entity + character_loader.load_character_data( + entity, + player.uuid().to_string(), + character_id, + ); + + // Start inserting non-persisted/default components for the entity + // while we load the DB data + server_emitter.emit(ServerEvent::InitCharacterData { + entity, + character_id, + }); + + // Give the player a welcome message + if !editable_settings.server_description.is_empty() { + client.send_msg( + ChatType::CommandInfo.server_msg(String::from( + &*editable_settings.server_description, + )), + ); + } + + // Only send login message if it wasn't already + // sent previously + if !client.login_msg_sent { + if let Some(player_uid) = uids.get(entity) { + new_chat_msgs.push((None, UnresolvedChatMsg { + chat_type: ChatType::Online(*player_uid), + message: "".to_string(), + })); + + client.login_msg_sent = true; + } + } + } else { + client.send_not_in_game(ServerNotInGameMsg::CharacterDataLoadError( + String::from("Failed to fetch player entity"), + )) + } + } else { + let registered = client.registered; + let in_game = client.in_game; + debug!(?registered, ?in_game, "dropped Character msg from client"); + } + }, + ClientNotInGameMsg::RequestCharacterList => { + if let Some(player) = players.get(entity) { + character_loader.load_character_list(entity, player.uuid().to_string()) + } + }, + ClientNotInGameMsg::CreateCharacter { alias, tool, body } => { + if let Err(error) = alias_validator.validate(&alias) { + debug!(?error, ?alias, "denied alias as it contained a banned word"); + client.send_not_in_game(ServerNotInGameMsg::CharacterActionError( + error.to_string(), + )); + } else if let Some(player) = players.get(entity) { + character_creator::create_character( + entity, + player.uuid().to_string(), + alias, + tool, + body, + character_loader, + ); + } + }, + ClientNotInGameMsg::DeleteCharacter(character_id) => { + if let Some(player) = players.get(entity) { + character_loader.delete_character( + entity, + player.uuid().to_string(), + character_id, + ); + } + }, + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> { + match msg { + PingMsg::Ping => client.send_ping(PingMsg::Pong), + PingMsg::Pong => {}, + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_register_msg( + player_list: &HashMap, + new_players: &mut Vec, + entity: specs::Entity, + client: &mut Client, + player_metrics: &ReadExpect<'_, PlayerMetrics>, + login_provider: &mut WriteExpect<'_, LoginProvider>, + admins: &mut WriteStorage<'_, Admin>, + players: &mut WriteStorage<'_, Player>, + settings: &Read<'_, Settings>, + editable_settings: &ReadExpect<'_, EditableSettings>, + msg: ClientRegisterMsg, + ) -> Result<(), crate::error::Error> { + let (username, uuid) = match login_provider.try_login( + &msg.token_or_username, + &*editable_settings.admins, + &*editable_settings.whitelist, + &*editable_settings.banlist, + ) { + Err(err) => { + client + .register_stream + .send(ServerRegisterAnswerMsg::Err(err))?; + return Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; + + let vd = msg + .view_distance + .map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); + let player = Player::new(username.clone(), None, vd, uuid); + let is_admin = editable_settings.admins.contains(&uuid); + + if !player.is_valid() { + // Invalid player + client.register_stream.send(ServerRegisterAnswerMsg::Err( + RegisterError::InvalidCharacter, + ))?; + return Ok(()); + } + + if !client.registered && client.in_game.is_none() { + // Add Player component to this client + let _ = players.insert(entity, player); + player_metrics.players_connected.inc(); + + // Give the Admin component to the player if their name exists in + // admin list + if is_admin { + let _ = admins.insert(entity, Admin); + } + + // Tell the client its request was successful. + client.registered = true; + client + .register_stream + .send(ServerRegisterAnswerMsg::Ok(()))?; + + // Send initial player list + client.send_msg(ServerMsg::PlayerListUpdate(PlayerListUpdate::Init( + player_list.clone(), + ))); + + // Add to list to notify all clients of the new player + new_players.push(entity); + } + + // Limit view distance if it's too high + // This comes after state registration so that the client actually hears it + if settings + .max_view_distance + .zip(msg.view_distance) + .map(|(max, vd)| vd > max) + .unwrap_or(false) + { + client.send_in_game(ServerInGameMsg::SetViewDistance( + settings.max_view_distance.unwrap_or(0), + )); + }; + Ok(()) + } + ///We needed to move this to a async fn, if we would use a async closures /// the compiler generates to much recursion and fails to compile this #[allow(clippy::too_many_arguments)] - async fn handle_client_msg( + async fn handle_messages( server_emitter: &mut common::event::Emitter<'_, ServerEvent>, new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, player_list: &HashMap, @@ -64,357 +487,87 @@ impl Sys { players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, + editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, - editable_settings: &EditableSettings, ) -> Result<(), crate::error::Error> { loop { - let msg = client.recv().await?; + let q1 = Client::internal_recv(&client.network_error, &mut client.singleton_stream); + let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream); + let q3 = Client::internal_recv(&client.network_error, &mut client.not_in_game_stream); + let q4 = Client::internal_recv(&client.network_error, &mut client.ping_stream); + let q5 = Client::internal_recv(&client.network_error, &mut client.register_stream); + + let (m1, m2, m3, m4, m5) = select!( + msg = q1.fuse() => (Some(msg?), None, None, None, None), + msg = q2.fuse() => (None, Some(msg?), None, None, None), + msg = q3.fuse() => (None, None, Some(msg?), None, None), + msg = q4.fuse() => (None, None, None, Some(msg?), None), + msg = q5.fuse() => (None, None, None, None,Some(msg?)), + ); *cnt += 1; - match msg { - // Go back to registered state (char selection screen) - ClientMsg::ExitIngame => match client.client_state { - // Use ClientMsg::Register instead. - ClientState::Connected => client.error_state(RequestStateError::WrongMessage), - ClientState::Registered => client.error_state(RequestStateError::Already), - ClientState::Spectator | ClientState::Character => { - server_emitter.emit(ServerEvent::ExitIngame { entity }); - }, - ClientState::Pending => {}, - }, - // Request spectator state - ClientMsg::Spectate => match client.client_state { - // Become Registered first. - ClientState::Connected => client.error_state(RequestStateError::Impossible), - ClientState::Spectator => client.error_state(RequestStateError::Already), - ClientState::Registered | ClientState::Character => { - client.allow_state(ClientState::Spectator) - }, - ClientState::Pending => {}, - }, - // Request registered state (login) - ClientMsg::Register { - view_distance, - token_or_username, - } => { - let (username, uuid) = match login_provider.try_login( - &token_or_username, - &*editable_settings.admins, - &*editable_settings.whitelist, - &*editable_settings.banlist, - ) { - Err(err) => { - client.error_state(RequestStateError::RegisterDenied(err)); - break Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; - - let vd = - view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); - let is_admin = editable_settings.admins.contains(&uuid); - let player = Player::new(username.clone(), None, vd, uuid); - - if !player.is_valid() { - // Invalid player - client.error_state(RequestStateError::Impossible); - break Ok(()); - } - - match client.client_state { - ClientState::Connected => { - // Add Player component to this client - let _ = players.insert(entity, player); - player_metrics.players_connected.inc(); - - // Give the Admin component to the player if their name exists in - // admin list - if is_admin { - let _ = admins.insert(entity, Admin); - } - - // Tell the client its request was successful. - client.allow_state(ClientState::Registered); - - // Send initial player list - client.notify(ServerMsg::PlayerListUpdate(PlayerListUpdate::Init( - player_list.clone(), - ))); - - // Add to list to notify all clients of the new player - new_players.push(entity); - }, - // Use RequestState instead (No need to send `player` again). - _ => client.error_state(RequestStateError::Impossible), - } - //client.allow_state(ClientState::Registered); - - // Limit view distance if it's too high - // This comes after state registration so that the client actually hears it - if settings - .max_view_distance - .zip(view_distance) - .map(|(max, vd)| vd > max) - .unwrap_or(false) - { - client.notify(ServerMsg::SetViewDistance( - settings.max_view_distance.unwrap_or(0), - )); - }; - }, - ClientMsg::SetViewDistance(view_distance) => { - if let ClientState::Character { .. } = client.client_state { - players.get_mut(entity).map(|player| { - player.view_distance = Some( - settings - .max_view_distance - .map(|max| view_distance.min(max)) - .unwrap_or(view_distance), - ) - }); - - if settings - .max_view_distance - .map(|max| view_distance > max) - .unwrap_or(false) - { - client.notify(ServerMsg::SetViewDistance( - settings.max_view_distance.unwrap_or(0), - )); - } - } - }, - ClientMsg::Character(character_id) => match client.client_state { - // Become Registered first. - ClientState::Connected => client.error_state(RequestStateError::Impossible), - ClientState::Registered | ClientState::Spectator => { - // Only send login message if it wasn't already - // sent previously - if let Some(player) = players.get(entity) { - // Send a request to load the character's component data from the - // DB. Once loaded, persisted components such as stats and inventory - // will be inserted for the entity - character_loader.load_character_data( - entity, - player.uuid().to_string(), - character_id, - ); - - // Start inserting non-persisted/default components for the entity - // while we load the DB data - server_emitter.emit(ServerEvent::InitCharacterData { - entity, - character_id, - }); - - // Give the player a welcome message - if !editable_settings.server_description.is_empty() { - client.notify(ChatType::CommandInfo.server_msg(String::from( - &*editable_settings.server_description, - ))); - } - - // Only send login message if it wasn't already - // sent previously - if !client.login_msg_sent { - if let Some(player_uid) = uids.get(entity) { - new_chat_msgs.push((None, UnresolvedChatMsg { - chat_type: ChatType::Online(*player_uid), - message: "".to_string(), - })); - - client.login_msg_sent = true; - } - } - } else { - client.notify(ServerMsg::CharacterDataLoadError(String::from( - "Failed to fetch player entity", - ))) - } - }, - ClientState::Character => client.error_state(RequestStateError::Already), - ClientState::Pending => {}, - }, - ClientMsg::ControllerInputs(inputs) => match client.client_state { - ClientState::Connected | ClientState::Registered | ClientState::Spectator => { - client.error_state(RequestStateError::Impossible) - }, - ClientState::Character => { - if let Some(controller) = controllers.get_mut(entity) { - controller.inputs.update_with_new(inputs); - } - }, - ClientState::Pending => {}, - }, - ClientMsg::ControlEvent(event) => match client.client_state { - ClientState::Connected | ClientState::Registered | ClientState::Spectator => { - client.error_state(RequestStateError::Impossible) - }, - ClientState::Character => { - // Skip respawn if client entity is alive - if let ControlEvent::Respawn = event { - if stats.get(entity).map_or(true, |s| !s.is_dead) { - continue; - } - } - if let Some(controller) = controllers.get_mut(entity) { - controller.events.push(event); - } - }, - ClientState::Pending => {}, - }, - ClientMsg::ControlAction(event) => match client.client_state { - ClientState::Connected | ClientState::Registered | ClientState::Spectator => { - client.error_state(RequestStateError::Impossible) - }, - ClientState::Character => { - if let Some(controller) = controllers.get_mut(entity) { - controller.actions.push(event); - } - }, - ClientState::Pending => {}, - }, - ClientMsg::ChatMsg(message) => match client.client_state { - ClientState::Connected => client.error_state(RequestStateError::Impossible), - ClientState::Registered | ClientState::Spectator | ClientState::Character => { - match validate_chat_msg(&message) { - Ok(()) => { - if let Some(from) = uids.get(entity) { - let mode = chat_modes.get(entity).cloned().unwrap_or_default(); - let msg = mode.new_message(*from, message); - new_chat_msgs.push((Some(entity), msg)); - } else { - error!("Could not send message. Missing player uid"); - } - }, - Err(ChatMsgValidationError::TooLong) => { - let max = MAX_BYTES_CHAT_MSG; - let len = message.len(); - warn!(?len, ?max, "Received a chat message that's too long") - }, - } - }, - ClientState::Pending => {}, - }, - ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state { - ClientState::Character => { - if force_updates.get(entity).is_none() - && stats.get(entity).map_or(true, |s| !s.is_dead) - { - let _ = positions.insert(entity, pos); - let _ = velocities.insert(entity, vel); - let _ = orientations.insert(entity, ori); - } - }, - // Only characters can send positions. - _ => client.error_state(RequestStateError::Impossible), - }, - ClientMsg::BreakBlock(pos) => { - if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { - block_changes.set(pos, block.into_vacant()); - } - }, - ClientMsg::PlaceBlock(pos, block) => { - if can_build.get(entity).is_some() { - block_changes.try_set(pos, block); - } - }, - ClientMsg::TerrainChunkRequest { key } => match client.client_state { - ClientState::Connected | ClientState::Registered => { - network_metrics.chunks_request_dropped.inc(); - client.error_state(RequestStateError::Impossible); - }, - ClientState::Spectator | ClientState::Character => { - let in_vd = if let (Some(view_distance), Some(pos)) = ( - players.get(entity).and_then(|p| p.view_distance), - positions.get(entity), - ) { - pos.0.xy().map(|e| e as f64).distance( - key.map(|e| e as f64 + 0.5) - * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), - ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) - * TerrainChunkSize::RECT_SIZE.x as f64 - } else { - true - }; - if in_vd { - match terrain.get_key(key) { - Some(chunk) => { - network_metrics.chunks_served_from_memory.inc(); - client.notify(ServerMsg::TerrainChunkUpdate { - key, - chunk: Ok(Box::new(chunk.clone())), - }) - }, - None => { - network_metrics.chunks_generation_triggered.inc(); - server_emitter.emit(ServerEvent::ChunkRequest(entity, key)) - }, - } - } else { - network_metrics.chunks_request_dropped.inc(); - } - }, - ClientState::Pending => {}, - }, - // Always possible. - ClientMsg::Ping => client.notify(ServerMsg::Pong), - ClientMsg::Pong => {}, - ClientMsg::Disconnect => { - client.notify(ServerMsg::Disconnect(DisconnectReason::Requested)); - }, - ClientMsg::Terminate => { - debug!(?entity, "Client send message to termitate session"); - player_metrics - .clients_disconnected - .with_label_values(&["gracefully"]) - .inc(); - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - break Ok(()); - }, - ClientMsg::RequestCharacterList => { - if let Some(player) = players.get(entity) { - character_loader.load_character_list(entity, player.uuid().to_string()) - } - }, - ClientMsg::CreateCharacter { alias, tool, body } => { - if let Err(error) = alias_validator.validate(&alias) { - debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.notify(ServerMsg::CharacterActionError(error.to_string())); - } else if let Some(player) = players.get(entity) { - character_creator::create_character( - entity, - player.uuid().to_string(), - alias, - tool, - body, - character_loader, - ); - } - }, - ClientMsg::DeleteCharacter(character_id) => { - if let Some(player) = players.get(entity) { - character_loader.delete_character( - entity, - player.uuid().to_string(), - character_id, - ); - } - }, - ClientMsg::UnlockSkill(skill) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.unlock_skill(skill)); - }, - ClientMsg::RefundSkill(skill) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.refund_skill(skill)); - }, - ClientMsg::UnlockSkillGroup(skill_group_type) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); - }, + if let Some(msg) = m1 { + Self::handle_client_msg( + server_emitter, + new_chat_msgs, + entity, + client, + player_metrics, + uids, + chat_modes, + msg, + )?; + } + if let Some(msg) = m2 { + Self::handle_client_in_game_msg( + server_emitter, + entity, + client, + terrain, + network_metrics, + can_build, + force_updates, + stats, + block_changes, + positions, + velocities, + orientations, + players, + controllers, + settings, + msg, + )?; + } + if let Some(msg) = m3 { + Self::handle_client_not_in_game_msg( + server_emitter, + new_chat_msgs, + entity, + client, + character_loader, + uids, + players, + editable_settings, + alias_validator, + msg, + )?; + } + if let Some(msg) = m4 { + Self::handle_ping_msg(client, msg)?; + } + if let Some(msg) = m5 { + Self::handle_register_msg( + player_list, + new_players, + entity, + client, + player_metrics, + login_provider, + admins, + players, + settings, + editable_settings, + msg, + )?; } } } @@ -448,8 +601,8 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Client>, WriteStorage<'a, Controller>, Read<'a, Settings>, - ReadExpect<'a, AliasValidator>, ReadExpect<'a, EditableSettings>, + ReadExpect<'a, AliasValidator>, ); #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 @@ -481,8 +634,8 @@ impl<'a> System<'a> for Sys { mut clients, mut controllers, settings, - alias_validator, editable_settings, + alias_validator, ): Self::SystemData, ) { span!(_guard, "run", "message::Sys::run"); @@ -515,7 +668,7 @@ impl<'a> System<'a> for Sys { let network_err: Result<(), crate::error::Error> = block_on(async { //TIMEOUT 0.02 ms for msg handling - let work_future = Self::handle_client_msg( + let work_future = Self::handle_messages( &mut server_emitter, &mut new_chat_msgs, &player_list, @@ -541,8 +694,8 @@ impl<'a> System<'a> for Sys { &mut players, &mut controllers, &settings, - &alias_validator, &editable_settings, + &alias_validator, ); select!( _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), @@ -573,7 +726,7 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::ClientDisconnect(entity)); } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { // Try pinging the client if the timeout is nearing. - client.notify(ServerMsg::Ping); + client.send_ping(PingMsg::Ping); } } @@ -587,8 +740,8 @@ impl<'a> System<'a> for Sys { is_admin: admins.get(entity).is_some(), character: None, // new players will be on character select. })); - for client in (&mut clients).join().filter(|c| c.is_registered()) { - client.notify(msg.clone()) + for client in (&mut clients).join().filter(|c| c.registered) { + client.send_msg(msg.clone()) } } } diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index 1e0df762bd..2ee739bada 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -80,7 +80,7 @@ impl<'a> System<'a> for Sys { ) .join() .filter_map(|(client, s, pos, player, e)| { - if client.is_ingame() { + if client.in_game.is_some() { player.view_distance.map(|v| (client, s, pos, v, e)) } else { None @@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys { .map(|key| subscription.regions.contains(key)) .unwrap_or(false) { - client.notify(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerMsg::DeleteEntity(uid)); } } }, @@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys { } // Tell client to delete entities in the region for (&uid, _) in (&uids, region.entities()).join() { - client.notify(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerMsg::DeleteEntity(uid)); } } // Send deleted entities since they won't be processed for this client in entity @@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys { .iter() .flat_map(|v| v.iter()) { - client.notify(ServerMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerMsg::DeleteEntity(Uid(*uid))); } } @@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys { { // Send message to create entity and tracked components and physics // components - client.notify(ServerMsg::CreateEntity( + client.send_msg(ServerMsg::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), @@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { .join() { // Send message to create entity and tracked components and physics components - client.notify(ServerMsg::CreateEntity( + client.send_msg(ServerMsg::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 9be68b799b..8ca477e0fd 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,7 +4,7 @@ use common::{ comp::{self, bird_medium, Alignment, Player, Pos}, event::{EventBus, ServerEvent}, generation::get_npc_name, - msg::ServerMsg, + msg::ServerInGameMsg, npc::NPC_NAMES, span, state::TerrainChanges, @@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys { Ok((chunk, supplement)) => (chunk, supplement), Err(Some(entity)) => { if let Some(client) = clients.get_mut(entity) { - client.notify(ServerMsg::TerrainChunkUpdate { + client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { key, chunk: Err(()), }); @@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys { .magnitude_squared(); if adjusted_dist_sqr <= view_distance.pow(2) { - client.notify(ServerMsg::TerrainChunkUpdate { + client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }); diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index 8d0e8b5f23..d0d1df5376 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos}, - msg::ServerMsg, + msg::ServerInGameMsg, span, state::TerrainChanges, terrain::TerrainGrid, @@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys { .map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd)) .unwrap_or(false) { - client.notify(ServerMsg::TerrainChunkUpdate { + client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { key: *chunk_key, chunk: Ok(Box::new(match terrain.get_key(*chunk_key) { Some(chunk) => chunk.clone(), @@ -51,10 +51,10 @@ impl<'a> System<'a> for Sys { // TODO: Don't send all changed blocks to all clients // Sync changed blocks - let msg = ServerMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); + let msg = ServerInGameMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); for (player, client) in (&players, &mut clients).join() { if player.view_distance.is_some() { - client.notify(msg.clone()); + client.send_in_game(msg.clone()); } } diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index d9b0f0e739..a30911f4cc 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -42,7 +42,7 @@ impl<'a> System<'a> for Sys { if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time)) { if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) { - client.notify(ServerMsg::Notification(Notification::WaypointSaved)); + client.send_msg(ServerMsg::Notification(Notification::WaypointSaved)); } } } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 1b9d85bf1f..4ec8eb27a7 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -10,7 +10,7 @@ use crate::{ Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{assets::Asset, comp, msg::ClientState, span, state::DeltaTime}; +use common::{assets::Asset, comp, span, state::DeltaTime}; use specs::WorldExt; use std::{cell::RefCell, rc::Rc}; use tracing::error; @@ -61,8 +61,8 @@ impl PlayState for CharSelectionState { fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { span!(_guard, "tick", "::tick"); - let client_state = self.client.borrow().get_client_state(); - if let ClientState::Pending | ClientState::Registered = client_state { + let client_in_game = self.client.borrow().get_in_game(); + if client_in_game.is_none() { // Handle window events for event in events { if self.char_selection_ui.handle_event(event.clone()) { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 0d0c39a118..6529573f08 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -20,7 +20,6 @@ use common::{ MAX_PICKUP_RANGE_SQR, }, event::EventBus, - msg::ClientState, outcome::Outcome, span, terrain::{Block, BlockKind}, @@ -213,8 +212,11 @@ impl PlayState for SessionState { // TODO: can this be a method on the session or are there borrowcheck issues? - let client_state = self.client.borrow().get_client_state(); - if let ClientState::Pending | ClientState::Character = client_state { + let (client_in_game, client_registered) = { + let client = self.client.borrow(); + (client.get_in_game(), client.get_registered()) + }; + if client_in_game.is_none() { // Update MyEntity // Note: Alternatively, the client could emit an event when the entity changes // which may or may not be more elegant @@ -1088,7 +1090,7 @@ impl PlayState for SessionState { self.cleanup(); PlayStateResult::Continue - } else if let ClientState::Registered = client_state { + } else if client_registered && client_in_game.is_none() { PlayStateResult::Switch(Box::new(CharSelectionState::new( global_state, Rc::clone(&self.client), From d7a74c0cf174abd2adb8ec2690f18dcec010db1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 5 Oct 2020 01:11:02 +0200 Subject: [PATCH 2/9] fix voxygen state system. before we had a timing error that we did the next voxygen step before client set in_game which lead us to a disconnect. now assume in_game is correct in client unless told by server in its answer --- client/src/lib.rs | 33 ++++++++++++++++---------- voxygen/src/menu/char_selection/mod.rs | 7 ++++-- voxygen/src/session.rs | 3 +-- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 74a7526ad7..c489d6c085 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -116,8 +116,8 @@ pub struct Client { singleton_stream: Stream, ping_stream: Stream, register_stream: Stream, - not_in_game_stream: Stream, in_game_stream: Stream, + not_in_game_stream: Stream, client_timeout: Duration, last_server_ping: f64, @@ -161,8 +161,8 @@ impl Client { let stream = block_on(participant.opened())?; let mut ping_stream = block_on(participant.opened())?; let mut register_stream = block_on(participant.opened())?; - let not_in_game_stream = block_on(participant.opened())?; let in_game_stream = block_on(participant.opened())?; + let not_in_game_stream = block_on(participant.opened())?; register_stream.send(ClientType::Game)?; let server_info: ServerInfo = block_on(register_stream.recv())?; @@ -456,7 +456,10 @@ impl Client { Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), Err(RegisterError::NotOnWhitelist) => Err(Error::NotOnWhitelist), Err(RegisterError::Banned(reason)) => Err(Error::Banned(reason)), - Ok(()) => Ok(()), + Ok(()) => { + self.registered = true; + Ok(()) + }, } } @@ -466,6 +469,9 @@ impl Client { .send(ClientNotInGameMsg::Character(character_id)) .unwrap(); + //Assume we are in_game unless server tells us otherwise + self.client_ingame = Some(ClientIngame::Character); + self.active_character_id = Some(character_id); } @@ -1351,6 +1357,7 @@ impl Client { }, // Cleanup for when the client goes back to the `in_game = None` ServerInGameMsg::ExitInGameSuccess => { + self.client_ingame = None; self.clean_state(); }, ServerInGameMsg::InventoryUpdate(mut inventory, event) => { @@ -1409,11 +1416,13 @@ impl Client { self.character_list.error = Some(error); }, ServerNotInGameMsg::CharacterDataLoadError(error) => { + trace!("Handling join error by server"); + self.client_ingame = None; self.clean_state(); self.character_list.error = Some(error); }, ServerNotInGameMsg::CharacterSuccess => { - warn!("WOOP88u8yeah"); + debug!("client is now in ingame state on server"); }, } Ok(()) @@ -1447,23 +1456,23 @@ impl Client { ) -> Result<(), Error> { loop { let (m1, m2, m3, m4) = select!( - msg = self.singleton_stream.recv().fuse() => (Some(msg?), None, None, None), - msg = self.ping_stream.recv().fuse() => (None, Some(msg?), None, None), - msg = self.not_in_game_stream.recv().fuse() => (None, None, Some(msg?), None), - msg = self.in_game_stream.recv().fuse() => (None, None, None, Some(msg?)), + msg = self.singleton_stream.recv().fuse() => (Some(msg), None, None, None), + msg = self.ping_stream.recv().fuse() => (None, Some(msg), None, None), + msg = self.not_in_game_stream.recv().fuse() => (None, None, Some(msg), None), + msg = self.in_game_stream.recv().fuse() => (None, None, None, Some(msg)), ); *cnt += 1; if let Some(msg) = m1 { - self.handle_server_msg(frontend_events, msg)?; + self.handle_server_msg(frontend_events, msg?)?; } if let Some(msg) = m2 { - self.handle_ping_msg(msg)?; + self.handle_ping_msg(msg?)?; } if let Some(msg) = m3 { - self.handle_server_not_in_game_msg(msg)?; + self.handle_server_not_in_game_msg(msg?)?; } if let Some(msg) = m4 { - self.handle_server_in_game_msg(frontend_events, msg)?; + self.handle_server_in_game_msg(frontend_events, msg?)?; } } } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 4ec8eb27a7..434c9c565f 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -61,8 +61,11 @@ impl PlayState for CharSelectionState { fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { span!(_guard, "tick", "::tick"); - let client_in_game = self.client.borrow().get_in_game(); - if client_in_game.is_none() { + let (client_in_game, client_registered) = { + let client = self.client.borrow(); + (client.get_in_game(), client.get_registered()) + }; + if client_in_game.is_none() && client_registered { // Handle window events for event in events { if self.char_selection_ui.handle_event(event.clone()) { diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 6529573f08..f4538232a8 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -211,12 +211,11 @@ impl PlayState for SessionState { )); // TODO: can this be a method on the session or are there borrowcheck issues? - let (client_in_game, client_registered) = { let client = self.client.borrow(); (client.get_in_game(), client.get_registered()) }; - if client_in_game.is_none() { + if client_in_game.is_some() { // Update MyEntity // Note: Alternatively, the client could emit an event when the entity changes // which may or may not be more elegant From 017e004309e55e286688c1b1cacf76f2520fb29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 5 Oct 2020 10:07:34 +0200 Subject: [PATCH 3/9] Rename enums to allow a super enum in the future, but i am not yet sure if we want to introduce this yet ``` //This is a helper structure, containing all possible data send over pub enum ClientMsg { Initial(ClientType), General(ClientGeneralMsg), InGame(ClientInGameMsg), NotInGame(ClientNotInGameMsg), Register(ClientRegisterMsg), Ping(PingMsg) } ``` --- client/src/lib.rs | 48 ++++++++++++------------ common/src/comp/chat.rs | 6 +-- common/src/msg/client.rs | 4 +- common/src/msg/mod.rs | 4 +- common/src/msg/server.rs | 6 +-- server/src/client.rs | 8 ++-- server/src/cmd.rs | 14 +++---- server/src/events/entity_manipulation.rs | 4 +- server/src/events/interaction.rs | 4 +- server/src/events/player.rs | 4 +- server/src/lib.rs | 8 ++-- server/src/state_ext.rs | 22 +++++------ server/src/sys/entity_sync.rs | 18 ++++----- server/src/sys/message.rs | 20 +++++----- server/src/sys/subscription.rs | 12 +++--- server/src/sys/waypoint.rs | 4 +- 16 files changed, 93 insertions(+), 93 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index c489d6c085..0f73686547 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,10 +25,10 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientInGameMsg, ClientIngame, ClientMsg, + validate_chat_msg, ChatMsgValidationError, ClientInGameMsg, ClientIngame, ClientGeneralMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerInGameMsg, - ServerInfo, ServerInitMsg, ServerMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, + ServerInfo, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, @@ -502,7 +502,7 @@ impl Client { /// Send disconnect message to the server pub fn request_logout(&mut self) { debug!("Requesting logout from server"); - if let Err(e) = self.singleton_stream.send(ClientMsg::Disconnect) { + if let Err(e) = self.singleton_stream.send(ClientGeneralMsg::Disconnect) { error!( ?e, "Couldn't send disconnect package to server, did server close already?" @@ -844,7 +844,7 @@ impl Client { match validate_chat_msg(&message) { Ok(()) => self .singleton_stream - .send(ClientMsg::ChatMsg(message)) + .send(ClientGeneralMsg::ChatMsg(message)) .unwrap(), Err(ChatMsgValidationError::TooLong) => tracing::warn!( "Attempted to send a message that's too long (Over {} bytes)", @@ -1111,24 +1111,24 @@ impl Client { fn handle_server_msg( &mut self, frontend_events: &mut Vec, - msg: ServerMsg, + msg: ServerGeneralMsg, ) -> Result<(), Error> { match msg { - ServerMsg::Disconnect(reason) => match reason { + ServerGeneralMsg::Disconnect(reason) => match reason { DisconnectReason::Shutdown => return Err(Error::ServerShutdown), DisconnectReason::Requested => { debug!("finally sending ClientMsg::Terminate"); frontend_events.push(Event::Disconnect); - self.singleton_stream.send(ClientMsg::Terminate)?; + self.singleton_stream.send(ClientGeneralMsg::Terminate)?; }, DisconnectReason::Kicked(reason) => { debug!("sending ClientMsg::Terminate because we got kicked"); frontend_events.push(Event::Kicked(reason.clone())); - self.singleton_stream.send(ClientMsg::Terminate)?; + self.singleton_stream.send(ClientGeneralMsg::Terminate)?; }, }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => self.player_list = list, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => self.player_list = list, + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) { warn!( "Received msg to insert {} with uid {} into the player list but there was \ @@ -1137,7 +1137,7 @@ impl Client { ); } }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.is_admin = admin; } else { @@ -1148,7 +1148,7 @@ impl Client { ); } }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(uid, char_info)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(uid, char_info)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.character = Some(char_info); } else { @@ -1159,7 +1159,7 @@ impl Client { ); } }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.character = match &player_info.character { Some(character) => Some(common::msg::CharacterInfo { @@ -1178,7 +1178,7 @@ impl Client { }; } }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { // Instead of removing players, mark them as offline because we need to // remember the names of disconnected players in chat. // @@ -1203,7 +1203,7 @@ impl Client { ); } }, - ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.player_alias = new_name; } else { @@ -1214,38 +1214,38 @@ impl Client { ); } }, - ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), - ServerMsg::SetPlayerEntity(uid) => { + ServerGeneralMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), + ServerGeneralMsg::SetPlayerEntity(uid) => { if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { self.entity = entity; } else { return Err(Error::Other("Failed to find entity from uid.".to_owned())); } }, - ServerMsg::TimeOfDay(time_of_day) => { + ServerGeneralMsg::TimeOfDay(time_of_day) => { *self.state.ecs_mut().write_resource() = time_of_day; }, - ServerMsg::EntitySync(entity_sync_package) => { + ServerGeneralMsg::EntitySync(entity_sync_package) => { self.state .ecs_mut() .apply_entity_sync_package(entity_sync_package); }, - ServerMsg::CompSync(comp_sync_package) => { + ServerGeneralMsg::CompSync(comp_sync_package) => { self.state .ecs_mut() .apply_comp_sync_package(comp_sync_package); }, - ServerMsg::CreateEntity(entity_package) => { + ServerGeneralMsg::CreateEntity(entity_package) => { self.state.ecs_mut().apply_entity_package(entity_package); }, - ServerMsg::DeleteEntity(entity) => { + ServerGeneralMsg::DeleteEntity(entity) => { if self.uid() != Some(entity) { self.state .ecs_mut() .delete_entity_and_clear_from_uid_allocator(entity.0); } }, - ServerMsg::Notification(n) => { + ServerGeneralMsg::Notification(n) => { frontend_events.push(Event::Notification(n)); }, } @@ -1802,7 +1802,7 @@ impl Client { impl Drop for Client { fn drop(&mut self) { trace!("Dropping client"); - if let Err(e) = self.singleton_stream.send(ClientMsg::Disconnect) { + if let Err(e) = self.singleton_stream.send(ClientGeneralMsg::Disconnect) { warn!( ?e, "Error during drop of client, couldn't send disconnect package, is the connection \ diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 71af8a6960..0888a9e21b 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -1,4 +1,4 @@ -use crate::{comp::group::Group, msg::ServerMsg, sync::Uid}; +use crate::{comp::group::Group, msg::ServerGeneralMsg, sync::Uid}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -118,11 +118,11 @@ impl ChatType { } } impl ChatType { - pub fn server_msg(self, msg: S) -> ServerMsg + pub fn server_msg(self, msg: S) -> ServerGeneralMsg where S: Into, { - ServerMsg::ChatMsg(self.chat_msg(msg)) + ServerGeneralMsg::ChatMsg(self.chat_msg(msg)) } } // Stores chat text, type diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 5e464fc5b9..23d904760a 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -63,9 +63,9 @@ pub enum ClientInGameMsg { /// Messages sent from the client to the server #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClientMsg { +pub enum ClientGeneralMsg { ChatMsg(String), Command(String), Disconnect, Terminate, -} +} \ No newline at end of file diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 964ee157fc..39fa3fc106 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -4,11 +4,11 @@ pub mod server; // Reexports pub use self::{ - client::{ClientInGameMsg, ClientMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType}, + client::{ClientInGameMsg, ClientGeneralMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType}, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerInGameMsg, ServerInfo, ServerInitMsg, ServerMsg, ServerNotInGameMsg, + RegisterError, ServerInGameMsg, ServerInfo, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, }, }; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 8aaa9873c4..542d0a64f2 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -255,7 +255,7 @@ pub enum ServerInGameMsg { /// Messages sent from the server to the client #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerMsg { +pub enum ServerGeneralMsg { PlayerListUpdate(PlayerListUpdate), /// A message to go into the client chat box. The client is responsible for /// formatting the message and turning it into a speech bubble. @@ -285,6 +285,6 @@ impl From for RegisterError { fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) } } -impl From for ServerMsg { - fn from(v: comp::ChatMsg) -> Self { ServerMsg::ChatMsg(v) } +impl From for ServerGeneralMsg { + fn from(v: comp::ChatMsg) -> Self { ServerGeneralMsg::ChatMsg(v) } } diff --git a/server/src/client.rs b/server/src/client.rs index 7423589d1f..8c320280e7 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,7 +1,7 @@ use crate::error::Error; use common::msg::{ - ClientInGameMsg, ClientIngame, ClientMsg, ClientNotInGameMsg, ClientType, PingMsg, - ServerInGameMsg, ServerInitMsg, ServerMsg, ServerNotInGameMsg, + ClientInGameMsg, ClientIngame, ClientGeneralMsg, ClientNotInGameMsg, ClientType, PingMsg, + ServerInGameMsg, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, }; use hashbrown::HashSet; use network::{MessageBuffer, Participant, Stream}; @@ -57,7 +57,7 @@ impl Client { Self::internal_send(&self.network_error, &mut self.register_stream, msg); } - pub fn send_msg(&mut self, msg: ServerMsg) { + pub fn send_msg(&mut self, msg: ServerGeneralMsg) { Self::internal_send(&self.network_error, &mut self.singleton_stream, msg); } @@ -95,7 +95,7 @@ impl Client { } } - pub async fn recv_msg(&mut self) -> Result { + pub async fn recv_msg(&mut self) -> Result { Self::internal_recv(&self.network_error, &mut self.singleton_stream).await } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index ccd9ca219f..7ebded8f51 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerInGameMsg, ServerMsg}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -505,7 +505,7 @@ fn handle_alias( old_alias_optional, ) { let msg = - ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(*uid, player.alias.clone())); + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(*uid, player.alias.clone())); server.state.notify_registered_clients(msg); // Announce alias change if target has a Body. @@ -1157,7 +1157,7 @@ fn handle_waypoint( .write_storage::() .insert(target, comp::Waypoint::new(pos.0, *time)); server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!")); - server.notify_client(client, ServerMsg::Notification(Notification::WaypointSaved)); + server.notify_client(client, ServerGeneralMsg::Notification(Notification::WaypointSaved)); }, None => server.notify_client( client, @@ -1193,7 +1193,7 @@ fn handle_adminify( ecs.write_storage().insert(player, comp::Admin).is_ok() }; // Update player list so the player shows up as admin in client chat. - let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin( + let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin( *ecs.read_storage::() .get(player) .expect("Player should have uid"), @@ -1586,7 +1586,7 @@ fn find_target( ecs: &specs::World, opt_alias: Option, fallback: EcsEntity, -) -> Result { +) -> Result { if let Some(alias) = opt_alias { (&ecs.entities(), &ecs.read_storage::()) .join() @@ -1658,7 +1658,7 @@ fn handle_set_level( .expect("Failed to get uid for player"); server .state - .notify_registered_clients(ServerMsg::PlayerListUpdate( + .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( PlayerListUpdate::LevelChange(uid, lvl), )); @@ -1898,7 +1898,7 @@ fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) { .emit_now(ServerEvent::ClientDisconnect(target_player)); server.notify_client( target_player, - ServerMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())), + ServerGeneralMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())), ); } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index edb13243eb..6b890a7515 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerInGameMsg, ServerMsg}, + msg::{PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { server .state - .notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange( + .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange( *uid, new_level, ))); } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index e34ea80d52..2496c0f7fa 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -4,7 +4,7 @@ use crate::{ }; use common::{ comp::{self, item}, - msg::ServerMsg, + msg::ServerGeneralMsg, sync::{Uid, WorldSyncExt}, }; use specs::{world::WorldExt, Entity as EcsEntity}; @@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { let mut clients = ecs.write_storage::(); if clients.get_mut(possesse).is_none() { if let Some(mut client) = clients.remove(possessor) { - client.send_msg(ServerMsg::SetPlayerEntity(possesse_uid)); + client.send_msg(ServerGeneralMsg::SetPlayerEntity(possesse_uid)); clients .insert(possesse, client) .err() diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 910e932131..2a73174b50 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerInGameMsg, ServerMsg}, + msg::{PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, span, sync::{Uid, UidAllocator}, }; @@ -131,7 +131,7 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg("")); state - .notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(*uid))); + .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(*uid))); } // Make sure to remove the player from the logged in list. (See LoginProvider) diff --git a/server/src/lib.rs b/server/src/lib.rs index c0b0e25526..3aa3524a58 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -46,7 +46,7 @@ use common::{ event::{EventBus, ServerEvent}, msg::{ server::WorldMapMsg, ClientType, DisconnectReason, ServerInGameMsg, ServerInfo, - ServerInitMsg, ServerMsg, ServerNotInGameMsg, + ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -885,7 +885,7 @@ impl Server { pub fn notify_client(&self, entity: EcsEntity, msg: S) where - S: Into, + S: Into, { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { client.send_msg(msg.into()) @@ -910,7 +910,7 @@ impl Server { } } - pub fn notify_registered_clients(&mut self, msg: ServerMsg) { + pub fn notify_registered_clients(&mut self, msg: ServerGeneralMsg) { self.state.notify_registered_clients(msg); } @@ -990,7 +990,7 @@ impl Server { impl Drop for Server { fn drop(&mut self) { self.state - .notify_registered_clients(ServerMsg::Disconnect(DisconnectReason::Shutdown)); + .notify_registered_clients(ServerGeneralMsg::Disconnect(DisconnectReason::Shutdown)); } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 14ef5bbf70..2554e71589 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -6,7 +6,7 @@ use common::{ comp, effect::Effect, msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerInGameMsg, ServerMsg, + CharacterInfo, ClientIngame, PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg, ServerNotInGameMsg, }, state::State, @@ -62,7 +62,7 @@ pub trait StateExt { fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); /// Iterates over registered clients and send each `ServerMsg` fn send_chat(&self, msg: comp::UnresolvedChatMsg); - fn notify_registered_clients(&self, msg: ServerMsg); + fn notify_registered_clients(&self, msg: ServerGeneralMsg); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( &mut self, @@ -229,7 +229,7 @@ impl StateExt for State { if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update - self.notify_registered_clients(ServerMsg::PlayerListUpdate( + self.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo { name: String::from(&stats.name), level: stats.level.level(), @@ -276,7 +276,7 @@ impl StateExt for State { | comp::ChatType::Kill(_, _) | comp::ChatType::Meta | comp::ChatType::World(_) => { - self.notify_registered_clients(ServerMsg::ChatMsg(resolved_msg)) + self.notify_registered_clients(ServerGeneralMsg::ChatMsg(resolved_msg)) }, comp::ChatType::Tell(u, t) => { for (client, uid) in ( @@ -286,7 +286,7 @@ impl StateExt for State { .join() { if uid == u || uid == t { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -298,7 +298,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } } @@ -310,7 +310,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } } @@ -322,7 +322,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } } @@ -336,7 +336,7 @@ impl StateExt for State { .join() { if s == &faction.0 { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -348,7 +348,7 @@ impl StateExt for State { .join() { if g == group { - client.send_msg(ServerMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -356,7 +356,7 @@ impl StateExt for State { } /// Sends the message to all connected clients - fn notify_registered_clients(&self, msg: ServerMsg) { + fn notify_registered_clients(&self, msg: ServerGeneralMsg) { for client in (&mut self.ecs().write_storage::()) .join() .filter(|c| c.registered) diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 3bd9b1d415..1faeca938d 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::{ServerInGameMsg, ServerMsg}, + msg::{ServerInGameMsg, ServerGeneralMsg}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -129,7 +129,7 @@ impl<'a> System<'a> for Sys { }) }) { let create_msg = - ServerMsg::CreateEntity(tracked_comps.create_entity_package( + ServerGeneralMsg::CreateEntity(tracked_comps.create_entity_package( entity, Some(*pos), vel.copied(), @@ -157,7 +157,7 @@ impl<'a> System<'a> for Sys { .map(|key| !regions.contains(key)) .unwrap_or(true) { - client.send_msg(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); } } } @@ -174,14 +174,14 @@ impl<'a> System<'a> for Sys { .take_deleted_in_region(key) .unwrap_or_default(), ); - let entity_sync_msg = ServerMsg::EntitySync(entity_sync_package); - let comp_sync_msg = ServerMsg::CompSync(comp_sync_package); + let entity_sync_msg = ServerGeneralMsg::EntitySync(entity_sync_package); + let comp_sync_msg = ServerGeneralMsg::CompSync(comp_sync_package); subscribers.iter_mut().for_each(move |(client, _, _, _)| { client.send_msg(entity_sync_msg.clone()); client.send_msg(comp_sync_msg.clone()); }); - let mut send_msg = |msg: ServerMsg, + let mut send_msg = |msg: ServerGeneralMsg, entity: EcsEntity, pos: Pos, force_update: Option<&ForceUpdate>, @@ -287,7 +287,7 @@ impl<'a> System<'a> for Sys { } send_msg( - ServerMsg::CompSync(comp_sync_package), + ServerGeneralMsg::CompSync(comp_sync_package), entity, pos, force_update, @@ -311,7 +311,7 @@ impl<'a> System<'a> for Sys { }) { for uid in &deleted { - client.send_msg(ServerMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); } } } @@ -353,7 +353,7 @@ impl<'a> System<'a> for Sys { // Sync resources // TODO: doesn't really belong in this system (rename system or create another // system?) - let tof_msg = ServerMsg::TimeOfDay(*time_of_day); + let tof_msg = ServerGeneralMsg::TimeOfDay(*time_of_day); for client in (&mut clients).join() { client.send_msg(tof_msg.clone()); } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 8cbbb947be..b44912695b 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -16,8 +16,8 @@ use common::{ event::{EventBus, ServerEvent}, msg::{ validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientInGameMsg, ClientIngame, - ClientMsg, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, PingMsg, PlayerInfo, - PlayerListUpdate, RegisterError, ServerInGameMsg, ServerMsg, ServerNotInGameMsg, + ClientGeneralMsg, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, PingMsg, PlayerInfo, + PlayerListUpdate, RegisterError, ServerInGameMsg, ServerGeneralMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, span, @@ -45,10 +45,10 @@ impl Sys { player_metrics: &ReadExpect<'_, PlayerMetrics>, uids: &ReadStorage<'_, Uid>, chat_modes: &ReadStorage<'_, ChatMode>, - msg: ClientMsg, + msg: ClientGeneralMsg, ) -> Result<(), crate::error::Error> { match msg { - ClientMsg::ChatMsg(message) => { + ClientGeneralMsg::ChatMsg(message) => { if client.registered { match validate_chat_msg(&message) { Ok(()) => { @@ -68,7 +68,7 @@ impl Sys { } } }, - ClientMsg::Command(message) => { + ClientGeneralMsg::Command(message) => { if client.registered { match validate_chat_msg(&message) { Ok(()) => { @@ -88,10 +88,10 @@ impl Sys { } } }, - ClientMsg::Disconnect => { - client.send_msg(ServerMsg::Disconnect(DisconnectReason::Requested)); + ClientGeneralMsg::Disconnect => { + client.send_msg(ServerGeneralMsg::Disconnect(DisconnectReason::Requested)); }, - ClientMsg::Terminate => { + ClientGeneralMsg::Terminate => { debug!(?entity, "Client send message to termitate session"); player_metrics .clients_disconnected @@ -435,7 +435,7 @@ impl Sys { .send(ServerRegisterAnswerMsg::Ok(()))?; // Send initial player list - client.send_msg(ServerMsg::PlayerListUpdate(PlayerListUpdate::Init( + client.send_msg(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init( player_list.clone(), ))); @@ -734,7 +734,7 @@ impl<'a> System<'a> for Sys { // Tell all clients to add them to the player list. for entity in new_players { if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { - let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { + let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { player_alias: player.alias.clone(), is_online: true, is_admin: admins.get(entity).is_some(), diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index 2ee739bada..ba5f3906df 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -5,7 +5,7 @@ use super::{ use crate::client::{self, Client, RegionSubscription}; use common::{ comp::{Ori, Player, Pos, Vel}, - msg::ServerMsg, + msg::ServerGeneralMsg, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, span, sync::Uid, @@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys { .map(|key| subscription.regions.contains(key)) .unwrap_or(false) { - client.send_msg(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); } } }, @@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys { } // Tell client to delete entities in the region for (&uid, _) in (&uids, region.entities()).join() { - client.send_msg(ServerMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); } } // Send deleted entities since they won't be processed for this client in entity @@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys { .iter() .flat_map(|v| v.iter()) { - client.send_msg(ServerMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); } } @@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys { { // Send message to create entity and tracked components and physics // components - client.send_msg(ServerMsg::CreateEntity( + client.send_msg(ServerGeneralMsg::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), @@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { .join() { // Send message to create entity and tracked components and physics components - client.send_msg(ServerMsg::CreateEntity( + client.send_msg(ServerGeneralMsg::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index a30911f4cc..ff86ab9bdf 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos, Waypoint, WaypointArea}, - msg::{Notification, ServerMsg}, + msg::{Notification, ServerGeneralMsg}, span, state::Time, }; @@ -42,7 +42,7 @@ impl<'a> System<'a> for Sys { if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time)) { if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) { - client.send_msg(ServerMsg::Notification(Notification::WaypointSaved)); + client.send_msg(ServerGeneralMsg::Notification(Notification::WaypointSaved)); } } } From 8b40f81ee2696ad82cb2f5bc42850a4b0f1ef1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 5 Oct 2020 12:44:33 +0200 Subject: [PATCH 4/9] No longer block the main thread for client connections, new clients will be handled by server without waiting. - Instread we have a dedicated thread that will async wait for new participants to connect and then notify the main thread - registry no longer sends a view distance with it. - remove ClientMsg::Command again as it's unused --- client/src/lib.rs | 27 ++-- common/src/msg/client.rs | 4 +- common/src/msg/mod.rs | 8 +- common/src/msg/server.rs | 1 + server/src/client.rs | 4 +- server/src/cmd.rs | 13 +- server/src/connection_handler.rs | 163 +++++++++++++++++++++++ server/src/events/entity_manipulation.rs | 8 +- server/src/events/player.rs | 7 +- server/src/lib.rs | 73 ++++------ server/src/state_ext.rs | 2 +- server/src/sys/entity_sync.rs | 9 +- server/src/sys/message.rs | 67 +++------- server/src/sys/waypoint.rs | 4 +- 14 files changed, 253 insertions(+), 137 deletions(-) create mode 100644 server/src/connection_handler.rs diff --git a/client/src/lib.rs b/client/src/lib.rs index 0f73686547..c3513a7083 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,10 +25,10 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientInGameMsg, ClientIngame, ClientGeneralMsg, + validate_chat_msg, ChatMsgValidationError, ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientNotInGameMsg, ClientRegisterMsg, ClientType, DisconnectReason, InviteAnswer, - Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerInGameMsg, - ServerInfo, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, + Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneralMsg, + ServerInGameMsg, ServerInfo, ServerInitMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, @@ -444,11 +444,8 @@ impl Client { } ).unwrap_or(Ok(username))?; - //TODO move ViewDistance out of register - self.register_stream.send(ClientRegisterMsg { - view_distance: self.view_distance, - token_or_username, - })?; + self.register_stream + .send(ClientRegisterMsg { token_or_username })?; match block_on(self.register_stream.recv::())? { Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn), @@ -1123,11 +1120,13 @@ impl Client { }, DisconnectReason::Kicked(reason) => { debug!("sending ClientMsg::Terminate because we got kicked"); - frontend_events.push(Event::Kicked(reason.clone())); + frontend_events.push(Event::Kicked(reason)); self.singleton_stream.send(ClientGeneralMsg::Terminate)?; }, }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => self.player_list = list, + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => { + self.player_list = list + }, ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) { warn!( @@ -1148,7 +1147,10 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(uid, char_info)) => { + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( + uid, + char_info, + )) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.character = Some(char_info); } else { @@ -1423,6 +1425,9 @@ impl Client { }, ServerNotInGameMsg::CharacterSuccess => { debug!("client is now in ingame state on server"); + if let Some(vd) = self.view_distance { + self.set_view_distance(vd); + } }, } Ok(()) diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 23d904760a..7e54b5e80b 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -20,7 +20,6 @@ pub enum ClientType { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ClientRegisterMsg { - pub view_distance: Option, pub token_or_username: String, } @@ -65,7 +64,6 @@ pub enum ClientInGameMsg { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ClientGeneralMsg { ChatMsg(String), - Command(String), Disconnect, Terminate, -} \ No newline at end of file +} diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 39fa3fc106..5ad5f5a5e9 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -4,12 +4,14 @@ pub mod server; // Reexports pub use self::{ - client::{ClientInGameMsg, ClientGeneralMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType}, + client::{ + ClientGeneralMsg, ClientInGameMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType, + }, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerInGameMsg, ServerInfo, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, - ServerRegisterAnswerMsg, + RegisterError, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, + ServerNotInGameMsg, ServerRegisterAnswerMsg, }, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 542d0a64f2..8a4fc851ef 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -194,6 +194,7 @@ pub enum DisconnectReason { /// Reponse To ClientType #[derive(Debug, Clone, Serialize, Deserialize)] +#[allow(clippy::clippy::large_enum_variant)] pub enum ServerInitMsg { TooManyPlayers, GameSync { diff --git a/server/src/client.rs b/server/src/client.rs index 8c320280e7..9835098cec 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,7 +1,7 @@ use crate::error::Error; use common::msg::{ - ClientInGameMsg, ClientIngame, ClientGeneralMsg, ClientNotInGameMsg, ClientType, PingMsg, - ServerInGameMsg, ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, + ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientNotInGameMsg, ClientType, PingMsg, + ServerGeneralMsg, ServerInGameMsg, ServerInitMsg, ServerNotInGameMsg, }; use hashbrown::HashSet; use network::{MessageBuffer, Participant, Stream}; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7ebded8f51..9f2fa203fc 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -504,8 +504,10 @@ fn handle_alias( ecs.read_storage::().get(target), old_alias_optional, ) { - let msg = - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(*uid, player.alias.clone())); + let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias( + *uid, + player.alias.clone(), + )); server.state.notify_registered_clients(msg); // Announce alias change if target has a Body. @@ -1157,7 +1159,10 @@ fn handle_waypoint( .write_storage::() .insert(target, comp::Waypoint::new(pos.0, *time)); server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!")); - server.notify_client(client, ServerGeneralMsg::Notification(Notification::WaypointSaved)); + server.notify_client( + client, + ServerGeneralMsg::Notification(Notification::WaypointSaved), + ); }, None => server.notify_client( client, diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs new file mode 100644 index 0000000000..cc7bb4255e --- /dev/null +++ b/server/src/connection_handler.rs @@ -0,0 +1,163 @@ +use crate::{Client, ClientType, ServerInfo}; +use crossbeam::{bounded, unbounded, Receiver, Sender}; +use futures_executor::block_on; +use futures_timer::Delay; +use futures_util::{select, FutureExt}; +use network::{Network, Participant, Promises}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, +}; +use tracing::{debug, error, trace, warn}; + +pub(crate) struct ServerInfoPacket { + pub info: ServerInfo, + pub time: f64, +} + +pub(crate) type ConnectionDataPacket = Client; + +pub(crate) struct ConnectionHandler { + _network: Arc, + thread_handle: Option>, + pub client_receiver: Receiver, + pub info_requester_receiver: Receiver>, + running: Arc, +} + +/// Instead of waiting the main loop we are handling connections, especially +/// their slow network .await part on a different thread. We need to communicate +/// to the Server main thread sometimes tough to get the current server_info and +/// time +impl ConnectionHandler { + pub fn new(network: Network) -> Self { + let network = Arc::new(network); + let network_clone = Arc::clone(&network); + let running = Arc::new(AtomicBool::new(true)); + let running_clone = Arc::clone(&running); + + let (client_sender, client_receiver) = unbounded::(); + let (info_requester_sender, info_requester_receiver) = + bounded::>(1); + + let thread_handle = Some(thread::spawn(|| { + block_on(Self::work( + network_clone, + client_sender, + info_requester_sender, + running_clone, + )); + })); + + Self { + _network: network, + thread_handle, + client_receiver, + info_requester_receiver, + running, + } + } + + async fn work( + network: Arc, + client_sender: Sender, + info_requester_sender: Sender>, + running: Arc, + ) { + while running.load(Ordering::Relaxed) { + const TIMEOUT: Duration = Duration::from_secs(5); + let participant = match select!( + _ = Delay::new(TIMEOUT).fuse() => None, + p = network.connected().fuse() => Some(p), + ) { + None => continue, //check condition + Some(Ok(p)) => p, + Some(Err(e)) => { + error!( + ?e, + "Stopping Conection Handler, no new connections can be made to server now!" + ); + break; + }, + }; + + let client_sender = client_sender.clone(); + let info_requester_sender = info_requester_sender.clone(); + + match Self::init_participant(participant, client_sender, info_requester_sender).await { + Ok(_) => (), + Err(e) => warn!(?e, "drop new participant, because an error occurred"), + } + } + } + + async fn init_participant( + participant: Participant, + client_sender: Sender, + info_requester_sender: Sender>, + ) -> Result<(), Box> { + debug!("New Participant connected to the server"); + let (sender, receiver) = bounded(1); + info_requester_sender.send(sender)?; + + let reliable = Promises::ORDERED | Promises::CONSISTENCY; + let reliablec = reliable | Promises::COMPRESSED; + + let general_stream = participant.open(10, reliablec).await?; + let ping_stream = participant.open(5, reliable).await?; + let mut register_stream = participant.open(10, reliablec).await?; + let in_game_stream = participant.open(10, reliablec).await?; + let not_in_game_stream = participant.open(10, reliablec).await?; + + let server_data = receiver.recv()?; + + register_stream.send(server_data.info)?; + + const TIMEOUT: Duration = Duration::from_secs(5); + let client_type = match select!( + _ = Delay::new(TIMEOUT).fuse() => None, + t = register_stream.recv::().fuse() => Some(t), + ) { + None => { + debug!("slow client connection detected, dropping it"); + return Ok(()); + }, + Some(client_type) => client_type?, + }; + + let client = Client { + registered: false, + client_type, + in_game: None, + participant: std::sync::Mutex::new(Some(participant)), + singleton_stream: general_stream, + ping_stream, + register_stream, + in_game_stream, + not_in_game_stream, + network_error: std::sync::atomic::AtomicBool::new(false), + last_ping: server_data.time, + login_msg_sent: false, + }; + + client_sender.send(client)?; + Ok(()) + } +} + +impl Drop for ConnectionHandler { + fn drop(&mut self) { + self.running.store(false, Ordering::Relaxed); + trace!("blocking till ConnectionHandler is closed"); + self.thread_handle + .take() + .unwrap() + .join() + .expect("There was an error in ConnectionHandler, clean shutdown impossible"); + trace!("gracefully closed ConnectionHandler!"); + } +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 6b890a7515..16eda3ef29 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, + msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { server .state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange( - *uid, new_level, - ))); + .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + PlayerListUpdate::LevelChange(*uid, new_level), + )); } diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 2a73174b50..572c5d9058 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg}, + msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, span, sync::{Uid, UidAllocator}, }; @@ -130,8 +130,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event ) { state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg("")); - state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(*uid))); + state.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + PlayerListUpdate::Remove(*uid), + )); } // Make sure to remove the player from the logged in list. (See LoginProvider) diff --git a/server/src/lib.rs b/server/src/lib.rs index 3aa3524a58..44795bf2cc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -9,6 +9,7 @@ mod character_creator; pub mod chunk_generator; pub mod client; pub mod cmd; +pub mod connection_handler; mod data_dir; pub mod error; pub mod events; @@ -35,6 +36,7 @@ use crate::{ chunk_generator::ChunkGenerator, client::{Client, RegionSubscription}, cmd::ChatCommandExt, + connection_handler::ConnectionHandler, data_dir::DataDir, login_provider::LoginProvider, state_ext::StateExt, @@ -45,8 +47,8 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ - server::WorldMapMsg, ClientType, DisconnectReason, ServerInGameMsg, ServerInfo, - ServerInitMsg, ServerGeneralMsg, ServerNotInGameMsg, + server::WorldMapMsg, ClientType, DisconnectReason, ServerGeneralMsg, ServerInGameMsg, + ServerInfo, ServerInitMsg, ServerNotInGameMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -56,10 +58,8 @@ use common::{ vol::{ReadVol, RectVolSize}, }; use futures_executor::block_on; -use futures_timer::Delay; -use futures_util::{select, FutureExt}; use metrics::{ServerMetrics, StateTickMetrics, TickMetrics}; -use network::{Network, Pid, Promises, ProtocolAddr}; +use network::{Network, Pid, ProtocolAddr}; use persistence::{ character_loader::{CharacterLoader, CharacterLoaderResponseType}, character_updater::CharacterUpdater, @@ -100,7 +100,7 @@ pub struct Server { index: IndexOwned, map: WorldMapMsg, - network: Network, + connection_handler: ConnectionHandler, thread_pool: ThreadPool, @@ -335,6 +335,7 @@ impl Server { .expect("Failed to initialize server metrics submodule."); thread_pool.execute(f); block_on(network.listen(ProtocolAddr::Tcp(settings.gameserver_address)))?; + let connection_handler = ConnectionHandler::new(network); let this = Self { state, @@ -342,7 +343,7 @@ impl Server { index, map, - network, + connection_handler, thread_pool, @@ -452,7 +453,7 @@ impl Server { let before_new_connections = Instant::now(); // 3) Handle inputs from clients - block_on(self.handle_new_connections(&mut frontend_events))?; + self.handle_new_connections(&mut frontend_events)?; let before_message_system = Instant::now(); @@ -794,59 +795,30 @@ impl Server { } /// Handle new client connections. - async fn handle_new_connections( - &mut self, - frontend_events: &mut Vec, - ) -> Result<(), Error> { - //TIMEOUT 0.1 ms for msg handling - const TIMEOUT: Duration = Duration::from_micros(100); - loop { - let participant = match select!( - _ = Delay::new(TIMEOUT).fuse() => None, - pr = self.network.connected().fuse() => Some(pr), - ) { - None => return Ok(()), - Some(pr) => pr?, - }; - debug!("New Participant connected to the server"); - let reliable = Promises::ORDERED | Promises::CONSISTENCY; - let reliablec = reliable | Promises::COMPRESSED; + fn handle_new_connections(&mut self, frontend_events: &mut Vec) -> Result<(), Error> { + while let Ok(sender) = self.connection_handler.info_requester_receiver.try_recv() { + // can fail, e.g. due to timeout or network prob. + trace!("sending info to connection_handler"); + let _ = sender.send(crate::connection_handler::ServerInfoPacket { + info: self.get_server_info(), + time: self.state.get_time(), + }); + } - let stream = participant.open(10, reliablec).await?; - let ping_stream = participant.open(5, reliable).await?; - let mut register_stream = participant.open(10, reliablec).await?; - let in_game_stream = participant.open(10, reliablec).await?; - let not_in_game_stream = participant.open(10, reliablec).await?; - - register_stream.send(self.get_server_info())?; - let client_type: ClientType = register_stream.recv().await?; + while let Ok(data) = self.connection_handler.client_receiver.try_recv() { + let mut client = data; if self.settings().max_players <= self.state.ecs().read_storage::().join().count() { trace!( - ?participant, + ?client.participant, "to many players, wont allow participant to connect" ); - register_stream.send(ServerInitMsg::TooManyPlayers)?; + client.register_stream.send(ServerInitMsg::TooManyPlayers)?; continue; } - let client = Client { - registered: false, - client_type, - in_game: None, - participant: std::sync::Mutex::new(Some(participant)), - singleton_stream: stream, - ping_stream, - register_stream, - in_game_stream, - not_in_game_stream, - network_error: std::sync::atomic::AtomicBool::new(false), - last_ping: self.state.get_time(), - login_msg_sent: false, - }; - let entity = self .state .ecs_mut() @@ -881,6 +853,7 @@ impl Server { frontend_events.push(Event::ClientConnected { entity }); debug!("Done initial sync with client."); } + Ok(()) } pub fn notify_client(&self, entity: EcsEntity, msg: S) diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 2554e71589..59166f7e77 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -6,7 +6,7 @@ use common::{ comp, effect::Effect, msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerInGameMsg, ServerGeneralMsg, + CharacterInfo, ClientIngame, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg, ServerNotInGameMsg, }, state::State, diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 1faeca938d..46a9e27d43 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::{ServerInGameMsg, ServerGeneralMsg}, + msg::{ServerGeneralMsg, ServerInGameMsg}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -128,13 +128,14 @@ impl<'a> System<'a> for Sys { (uid, pos, velocities.get(entity), orientations.get(entity)) }) }) { - let create_msg = - ServerGeneralMsg::CreateEntity(tracked_comps.create_entity_package( + let create_msg = ServerGeneralMsg::CreateEntity( + tracked_comps.create_entity_package( entity, Some(*pos), vel.copied(), ori.copied(), - )); + ), + ); for (client, regions, client_entity, _) in &mut subscribers { if maybe_key .as_ref() diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index b44912695b..0dbc528fee 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,10 +15,10 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientInGameMsg, ClientIngame, - ClientGeneralMsg, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, PingMsg, PlayerInfo, - PlayerListUpdate, RegisterError, ServerInGameMsg, ServerGeneralMsg, ServerNotInGameMsg, - ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneralMsg, + ClientInGameMsg, ClientIngame, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, + PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneralMsg, ServerInGameMsg, + ServerNotInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -68,26 +68,6 @@ impl Sys { } } }, - ClientGeneralMsg::Command(message) => { - if client.registered { - match validate_chat_msg(&message) { - Ok(()) => { - if let Some(from) = uids.get(entity) { - let mode = chat_modes.get(entity).cloned().unwrap_or_default(); - let msg = mode.new_message(*from, message); - new_chat_msgs.push((Some(entity), msg)); - } else { - error!("Could not send message. Missing player uid"); - } - }, - Err(ChatMsgValidationError::TooLong) => { - let max = MAX_BYTES_CHAT_MSG; - let len = message.len(); - warn!(?len, ?max, "Received a chat message that's too long") - }, - } - } - }, ClientGeneralMsg::Disconnect => { client.send_msg(ServerGeneralMsg::Disconnect(DisconnectReason::Requested)); }, @@ -122,7 +102,7 @@ impl Sys { settings: &Read<'_, Settings>, msg: ClientInGameMsg, ) -> Result<(), crate::error::Error> { - if !client.in_game.is_some() { + if client.in_game.is_none() { debug!(?entity, "client is not in_game, ignoring msg"); trace!(?msg, "ignored msg content"); if matches!(msg, ClientInGameMsg::TerrainChunkRequest{ .. }) { @@ -147,6 +127,7 @@ impl Sys { ) }); + //correct client if its VD is to high if settings .max_view_distance .map(|max| view_distance > max) @@ -278,7 +259,7 @@ impl Sys { } }, ClientNotInGameMsg::Character(character_id) => { - if client.registered && !client.in_game.is_some() { + if client.registered && client.in_game.is_none() { // Only send login message if it wasn't already // sent previously if let Some(player) = players.get(entity) { @@ -384,7 +365,6 @@ impl Sys { login_provider: &mut WriteExpect<'_, LoginProvider>, admins: &mut WriteStorage<'_, Admin>, players: &mut WriteStorage<'_, Player>, - settings: &Read<'_, Settings>, editable_settings: &ReadExpect<'_, EditableSettings>, msg: ClientRegisterMsg, ) -> Result<(), crate::error::Error> { @@ -403,10 +383,8 @@ impl Sys { Ok((username, uuid)) => (username, uuid), }; - let vd = msg - .view_distance - .map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); - let player = Player::new(username.clone(), None, vd, uuid); + const INITIAL_VD: Option = Some(5); //will be changed after login + let player = Player::new(username.clone(), None, INITIAL_VD, uuid); let is_admin = editable_settings.admins.contains(&uuid); if !player.is_valid() { @@ -442,19 +420,6 @@ impl Sys { // Add to list to notify all clients of the new player new_players.push(entity); } - - // Limit view distance if it's too high - // This comes after state registration so that the client actually hears it - if settings - .max_view_distance - .zip(msg.view_distance) - .map(|(max, vd)| vd > max) - .unwrap_or(false) - { - client.send_in_game(ServerInGameMsg::SetViewDistance( - settings.max_view_distance.unwrap_or(0), - )); - }; Ok(()) } @@ -564,7 +529,6 @@ impl Sys { login_provider, admins, players, - settings, editable_settings, msg, )?; @@ -734,12 +698,13 @@ impl<'a> System<'a> for Sys { // Tell all clients to add them to the player list. for entity in new_players { if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { - let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { - player_alias: player.alias.clone(), - is_online: true, - is_admin: admins.get(entity).is_some(), - character: None, // new players will be on character select. - })); + let msg = + ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { + player_alias: player.alias.clone(), + is_online: true, + is_admin: admins.get(entity).is_some(), + character: None, // new players will be on character select. + })); for client in (&mut clients).join().filter(|c| c.registered) { client.send_msg(msg.clone()) } diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index ff86ab9bdf..63642e135a 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -42,7 +42,9 @@ impl<'a> System<'a> for Sys { if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time)) { if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) { - client.send_msg(ServerGeneralMsg::Notification(Notification::WaypointSaved)); + client.send_msg(ServerGeneralMsg::Notification( + Notification::WaypointSaved, + )); } } } From e8452fafc668bafa1007834c4e696d356bd13fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Tue, 6 Oct 2020 13:15:20 +0200 Subject: [PATCH 5/9] fix naming, replace NotInGame with CharacterScreen --- client/src/lib.rs | 65 +++++++++++++++++--------------- common/src/msg/client.rs | 6 +-- common/src/msg/mod.rs | 6 +-- common/src/msg/server.rs | 6 +-- server/src/client.rs | 14 +++---- server/src/connection_handler.rs | 4 +- server/src/lib.rs | 22 +++++------ server/src/state_ext.rs | 6 +-- server/src/sys/message.rs | 37 +++++++++--------- 9 files changed, 86 insertions(+), 80 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index c3513a7083..14d051a333 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,11 +25,11 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientGeneralMsg, ClientInGameMsg, ClientIngame, - ClientNotInGameMsg, ClientRegisterMsg, ClientType, DisconnectReason, InviteAnswer, - Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneralMsg, - ServerInGameMsg, ServerInfo, ServerInitMsg, ServerNotInGameMsg, ServerRegisterAnswerMsg, - MAX_BYTES_CHAT_MSG, + validate_chat_msg, ChatMsgValidationError, ClientCharacterScreenMsg, ClientGeneralMsg, + ClientInGameMsg, ClientIngame, ClientRegisterMsg, ClientType, DisconnectReason, + InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, + ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, + ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, recipe::RecipeBook, @@ -113,11 +113,11 @@ pub struct Client { _network: Network, participant: Option, - singleton_stream: Stream, + general_stream: Stream, ping_stream: Stream, register_stream: Stream, + character_screen_stream: Stream, in_game_stream: Stream, - not_in_game_stream: Stream, client_timeout: Duration, last_server_ping: f64, @@ -161,8 +161,8 @@ impl Client { let stream = block_on(participant.opened())?; let mut ping_stream = block_on(participant.opened())?; let mut register_stream = block_on(participant.opened())?; + let character_screen_stream = block_on(participant.opened())?; let in_game_stream = block_on(participant.opened())?; - let not_in_game_stream = block_on(participant.opened())?; register_stream.send(ClientType::Game)?; let server_info: ServerInfo = block_on(register_stream.recv())?; @@ -397,10 +397,10 @@ impl Client { _network: network, participant: Some(participant), - singleton_stream: stream, + general_stream: stream, ping_stream, register_stream, - not_in_game_stream, + character_screen_stream, in_game_stream, client_timeout, @@ -462,8 +462,8 @@ impl Client { /// Request a state transition to `ClientState::Character`. pub fn request_character(&mut self, character_id: CharacterId) { - self.not_in_game_stream - .send(ClientNotInGameMsg::Character(character_id)) + self.character_screen_stream + .send(ClientCharacterScreenMsg::Character(character_id)) .unwrap(); //Assume we are in_game unless server tells us otherwise @@ -475,31 +475,31 @@ impl Client { /// Load the current players character list pub fn load_character_list(&mut self) { self.character_list.loading = true; - self.not_in_game_stream - .send(ClientNotInGameMsg::RequestCharacterList) + self.character_screen_stream + .send(ClientCharacterScreenMsg::RequestCharacterList) .unwrap(); } /// New character creation pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { self.character_list.loading = true; - self.not_in_game_stream - .send(ClientNotInGameMsg::CreateCharacter { alias, tool, body }) + self.character_screen_stream + .send(ClientCharacterScreenMsg::CreateCharacter { alias, tool, body }) .unwrap(); } /// Character deletion pub fn delete_character(&mut self, character_id: CharacterId) { self.character_list.loading = true; - self.not_in_game_stream - .send(ClientNotInGameMsg::DeleteCharacter(character_id)) + self.character_screen_stream + .send(ClientCharacterScreenMsg::DeleteCharacter(character_id)) .unwrap(); } /// Send disconnect message to the server pub fn request_logout(&mut self) { debug!("Requesting logout from server"); - if let Err(e) = self.singleton_stream.send(ClientGeneralMsg::Disconnect) { + if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { error!( ?e, "Couldn't send disconnect package to server, did server close already?" @@ -840,7 +840,7 @@ impl Client { pub fn send_chat(&mut self, message: String) { match validate_chat_msg(&message) { Ok(()) => self - .singleton_stream + .general_stream .send(ClientGeneralMsg::ChatMsg(message)) .unwrap(), Err(ChatMsgValidationError::TooLong) => tracing::warn!( @@ -1116,12 +1116,12 @@ impl Client { DisconnectReason::Requested => { debug!("finally sending ClientMsg::Terminate"); frontend_events.push(Event::Disconnect); - self.singleton_stream.send(ClientGeneralMsg::Terminate)?; + self.general_stream.send(ClientGeneralMsg::Terminate)?; }, DisconnectReason::Kicked(reason) => { debug!("sending ClientMsg::Terminate because we got kicked"); frontend_events.push(Event::Kicked(reason)); - self.singleton_stream.send(ClientGeneralMsg::Terminate)?; + self.general_stream.send(ClientGeneralMsg::Terminate)?; }, }, ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => { @@ -1407,23 +1407,26 @@ impl Client { Ok(()) } - fn handle_server_not_in_game_msg(&mut self, msg: ServerNotInGameMsg) -> Result<(), Error> { + fn handle_server_character_screen_msg( + &mut self, + msg: ServerCharacterScreenMsg, + ) -> Result<(), Error> { match msg { - ServerNotInGameMsg::CharacterListUpdate(character_list) => { + ServerCharacterScreenMsg::CharacterListUpdate(character_list) => { self.character_list.characters = character_list; self.character_list.loading = false; }, - ServerNotInGameMsg::CharacterActionError(error) => { + ServerCharacterScreenMsg::CharacterActionError(error) => { warn!("CharacterActionError: {:?}.", error); self.character_list.error = Some(error); }, - ServerNotInGameMsg::CharacterDataLoadError(error) => { + ServerCharacterScreenMsg::CharacterDataLoadError(error) => { trace!("Handling join error by server"); self.client_ingame = None; self.clean_state(); self.character_list.error = Some(error); }, - ServerNotInGameMsg::CharacterSuccess => { + ServerCharacterScreenMsg::CharacterSuccess => { debug!("client is now in ingame state on server"); if let Some(vd) = self.view_distance { self.set_view_distance(vd); @@ -1461,9 +1464,9 @@ impl Client { ) -> Result<(), Error> { loop { let (m1, m2, m3, m4) = select!( - msg = self.singleton_stream.recv().fuse() => (Some(msg), None, None, None), + msg = self.general_stream.recv().fuse() => (Some(msg), None, None, None), msg = self.ping_stream.recv().fuse() => (None, Some(msg), None, None), - msg = self.not_in_game_stream.recv().fuse() => (None, None, Some(msg), None), + msg = self.character_screen_stream.recv().fuse() => (None, None, Some(msg), None), msg = self.in_game_stream.recv().fuse() => (None, None, None, Some(msg)), ); *cnt += 1; @@ -1474,7 +1477,7 @@ impl Client { self.handle_ping_msg(msg?)?; } if let Some(msg) = m3 { - self.handle_server_not_in_game_msg(msg?)?; + self.handle_server_character_screen_msg(msg?)?; } if let Some(msg) = m4 { self.handle_server_in_game_msg(frontend_events, msg?)?; @@ -1807,7 +1810,7 @@ impl Client { impl Drop for Client { fn drop(&mut self) { trace!("Dropping client"); - if let Err(e) = self.singleton_stream.send(ClientGeneralMsg::Disconnect) { + if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { warn!( ?e, "Error during drop of client, couldn't send disconnect package, is the connection \ diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 7e54b5e80b..5e17c978ab 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -23,9 +23,9 @@ pub struct ClientRegisterMsg { pub token_or_username: String, } -//messages send by clients only valid when NOT ingame +//messages send by clients only valid when in character screen #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientNotInGameMsg { +pub enum ClientCharacterScreenMsg { RequestCharacterList, CreateCharacter { alias: String, @@ -37,7 +37,7 @@ pub enum ClientNotInGameMsg { Spectate, } -//messages send by clients only valid when ingame +//messages send by clients only valid when in game (with a character) #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ClientInGameMsg { ControllerInputs(comp::ControllerInputs), diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 5ad5f5a5e9..58367669d3 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -5,13 +5,13 @@ pub mod server; // Reexports pub use self::{ client::{ - ClientGeneralMsg, ClientInGameMsg, ClientNotInGameMsg, ClientRegisterMsg, ClientType, + ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientRegisterMsg, ClientType, }, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, - ServerNotInGameMsg, ServerRegisterAnswerMsg, + RegisterError, ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, + ServerInitMsg, ServerRegisterAnswerMsg, }, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 8a4fc851ef..59d2b0b91d 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -209,9 +209,9 @@ pub enum ServerInitMsg { pub type ServerRegisterAnswerMsg = Result<(), RegisterError>; -//Messages only allowed while client ingame +//Messages only allowed while client in character screen #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerNotInGameMsg { +pub enum ServerCharacterScreenMsg { /// An error occurred while loading character data CharacterDataLoadError(String), /// A list of characters belonging to the a authenticated player was sent @@ -221,7 +221,7 @@ pub enum ServerNotInGameMsg { CharacterSuccess, } -//Messages only allowed while client ingame +//Messages only allowed while client is in game (with a character) #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ServerInGameMsg { GroupUpdate(comp::group::ChangeNotification), diff --git a/server/src/client.rs b/server/src/client.rs index 9835098cec..a473c03c52 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,7 +1,7 @@ use crate::error::Error; use common::msg::{ - ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientNotInGameMsg, ClientType, PingMsg, - ServerGeneralMsg, ServerInGameMsg, ServerInitMsg, ServerNotInGameMsg, + ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientType, PingMsg, + ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInitMsg, }; use hashbrown::HashSet; use network::{MessageBuffer, Participant, Stream}; @@ -23,8 +23,8 @@ pub struct Client { pub singleton_stream: Stream, pub ping_stream: Stream, pub register_stream: Stream, + pub character_screen_stream: Stream, pub in_game_stream: Stream, - pub not_in_game_stream: Stream, pub network_error: AtomicBool, pub last_ping: f64, pub login_msg_sent: bool, @@ -65,8 +65,8 @@ impl Client { Self::internal_send(&self.network_error, &mut self.in_game_stream, msg); } - pub fn send_not_in_game(&mut self, msg: ServerNotInGameMsg) { - Self::internal_send(&self.network_error, &mut self.not_in_game_stream, msg); + pub fn send_character_screen(&mut self, msg: ServerCharacterScreenMsg) { + Self::internal_send(&self.network_error, &mut self.character_screen_stream, msg); } pub fn send_ping(&mut self, msg: PingMsg) { @@ -103,8 +103,8 @@ impl Client { Self::internal_recv(&self.network_error, &mut self.in_game_stream).await } - pub async fn recv_not_in_game_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.not_in_game_stream).await + pub async fn recv_character_screen_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.character_screen_stream).await } pub async fn recv_ping_msg(&mut self) -> Result { diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index cc7bb4255e..4e3db3b09a 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -110,8 +110,8 @@ impl ConnectionHandler { let general_stream = participant.open(10, reliablec).await?; let ping_stream = participant.open(5, reliable).await?; let mut register_stream = participant.open(10, reliablec).await?; + let character_screen_stream = participant.open(10, reliablec).await?; let in_game_stream = participant.open(10, reliablec).await?; - let not_in_game_stream = participant.open(10, reliablec).await?; let server_data = receiver.recv()?; @@ -138,7 +138,7 @@ impl ConnectionHandler { ping_stream, register_stream, in_game_stream, - not_in_game_stream, + character_screen_stream, network_error: std::sync::atomic::AtomicBool::new(false), last_ping: server_data.time, login_msg_sent: false, diff --git a/server/src/lib.rs b/server/src/lib.rs index 44795bf2cc..ce5d0b267e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -47,8 +47,8 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ - server::WorldMapMsg, ClientType, DisconnectReason, ServerGeneralMsg, ServerInGameMsg, - ServerInfo, ServerInitMsg, ServerNotInGameMsg, + server::WorldMapMsg, ClientType, DisconnectReason, ServerCharacterScreenMsg, + ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -525,13 +525,13 @@ impl Server { .messages() .for_each(|query_result| match query_result.result { CharacterLoaderResponseType::CharacterList(result) => match result { - Ok(character_list_data) => self.notify_not_in_game_client( + Ok(character_list_data) => self.notify_character_screen_client( query_result.entity, - ServerNotInGameMsg::CharacterListUpdate(character_list_data), + ServerCharacterScreenMsg::CharacterListUpdate(character_list_data), ), - Err(error) => self.notify_not_in_game_client( + Err(error) => self.notify_character_screen_client( query_result.entity, - ServerNotInGameMsg::CharacterActionError(error.to_string()), + ServerCharacterScreenMsg::CharacterActionError(error.to_string()), ), }, CharacterLoaderResponseType::CharacterData(result) => { @@ -544,9 +544,9 @@ impl Server { // We failed to load data for the character from the DB. Notify the // client to push the state back to character selection, with the error // to display - self.notify_not_in_game_client( + self.notify_character_screen_client( query_result.entity, - ServerNotInGameMsg::CharacterDataLoadError(error.to_string()), + ServerCharacterScreenMsg::CharacterDataLoadError(error.to_string()), ); // Clean up the entity data on the server @@ -874,12 +874,12 @@ impl Server { } } - pub fn notify_not_in_game_client(&self, entity: EcsEntity, msg: S) + pub fn notify_character_screen_client(&self, entity: EcsEntity, msg: S) where - S: Into, + S: Into, { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.send_not_in_game(msg.into()) + client.send_character_screen(msg.into()) } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 59166f7e77..2f31784f91 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -6,8 +6,8 @@ use common::{ comp, effect::Effect, msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg, - ServerNotInGameMsg, + CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreenMsg, ServerGeneralMsg, + ServerInGameMsg, }, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -220,7 +220,7 @@ impl StateExt for State { // Tell the client its request was successful. if let Some(client) = self.ecs().write_storage::().get_mut(entity) { client.in_game = Some(ClientIngame::Character); - client.send_not_in_game(ServerNotInGameMsg::CharacterSuccess) + client.send_character_screen(ServerCharacterScreenMsg::CharacterSuccess) } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 0dbc528fee..1dc5b98a44 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,10 +15,10 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneralMsg, - ClientInGameMsg, ClientIngame, ClientNotInGameMsg, ClientRegisterMsg, DisconnectReason, - PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneralMsg, ServerInGameMsg, - ServerNotInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreenMsg, + ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientRegisterMsg, DisconnectReason, + PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreenMsg, + ServerGeneralMsg, ServerInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -237,7 +237,7 @@ impl Sys { } #[allow(clippy::too_many_arguments)] - fn handle_client_not_in_game_msg( + fn handle_client_character_screen_msg( server_emitter: &mut common::event::Emitter<'_, ServerEvent>, new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, entity: specs::Entity, @@ -247,18 +247,18 @@ impl Sys { players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, - msg: ClientNotInGameMsg, + msg: ClientCharacterScreenMsg, ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientNotInGameMsg::Spectate => { + ClientCharacterScreenMsg::Spectate => { if client.registered { client.in_game = Some(ClientIngame::Spectator) } else { debug!("dropped Spectate msg from unregistered client"); } }, - ClientNotInGameMsg::Character(character_id) => { + ClientCharacterScreenMsg::Character(character_id) => { if client.registered && client.in_game.is_none() { // Only send login message if it wasn't already // sent previously @@ -301,9 +301,11 @@ impl Sys { } } } else { - client.send_not_in_game(ServerNotInGameMsg::CharacterDataLoadError( - String::from("Failed to fetch player entity"), - )) + client.send_character_screen( + ServerCharacterScreenMsg::CharacterDataLoadError(String::from( + "Failed to fetch player entity", + )), + ) } } else { let registered = client.registered; @@ -311,15 +313,15 @@ impl Sys { debug!(?registered, ?in_game, "dropped Character msg from client"); } }, - ClientNotInGameMsg::RequestCharacterList => { + ClientCharacterScreenMsg::RequestCharacterList => { if let Some(player) = players.get(entity) { character_loader.load_character_list(entity, player.uuid().to_string()) } }, - ClientNotInGameMsg::CreateCharacter { alias, tool, body } => { + ClientCharacterScreenMsg::CreateCharacter { alias, tool, body } => { if let Err(error) = alias_validator.validate(&alias) { debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.send_not_in_game(ServerNotInGameMsg::CharacterActionError( + client.send_character_screen(ServerCharacterScreenMsg::CharacterActionError( error.to_string(), )); } else if let Some(player) = players.get(entity) { @@ -333,7 +335,7 @@ impl Sys { ); } }, - ClientNotInGameMsg::DeleteCharacter(character_id) => { + ClientCharacterScreenMsg::DeleteCharacter(character_id) => { if let Some(player) = players.get(entity) { character_loader.delete_character( entity, @@ -458,7 +460,8 @@ impl Sys { loop { let q1 = Client::internal_recv(&client.network_error, &mut client.singleton_stream); let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream); - let q3 = Client::internal_recv(&client.network_error, &mut client.not_in_game_stream); + let q3 = + Client::internal_recv(&client.network_error, &mut client.character_screen_stream); let q4 = Client::internal_recv(&client.network_error, &mut client.ping_stream); let q5 = Client::internal_recv(&client.network_error, &mut client.register_stream); @@ -503,7 +506,7 @@ impl Sys { )?; } if let Some(msg) = m3 { - Self::handle_client_not_in_game_msg( + Self::handle_client_character_screen_msg( server_emitter, new_chat_msgs, entity, From ff374eab59cbfae9f4961d0d6a4ce8c87dfce837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Wed, 7 Oct 2020 12:31:49 +0200 Subject: [PATCH 6/9] create a ServerMsg and ClientMsg enum and verify the state when in debug mode to benefit from the transition --- client/src/lib.rs | 365 +++++++++++------------ common/src/comp/chat.rs | 6 +- common/src/msg/client.rs | 68 ++++- common/src/msg/mod.rs | 8 +- common/src/msg/server.rs | 292 +++++++----------- common/src/msg/world_packet.rs | 123 ++++++++ server/src/client.rs | 66 ++-- server/src/cmd.rs | 18 +- server/src/connection_handler.rs | 2 +- server/src/events/entity_manipulation.rs | 6 +- server/src/events/group_manip.rs | 18 +- server/src/events/interaction.rs | 4 +- server/src/events/inventory_manip.rs | 6 +- server/src/events/player.rs | 10 +- server/src/lib.rs | 44 +-- server/src/state_ext.rs | 41 ++- server/src/sys/entity_sync.rs | 27 +- server/src/sys/invite_timeout.rs | 4 +- server/src/sys/message.rs | 98 +++--- server/src/sys/subscription.rs | 12 +- server/src/sys/terrain.rs | 6 +- server/src/sys/terrain_sync.rs | 8 +- server/src/sys/waypoint.rs | 7 +- world/src/lib.rs | 2 +- world/src/sim/mod.rs | 2 +- 25 files changed, 668 insertions(+), 575 deletions(-) create mode 100644 common/src/msg/world_packet.rs diff --git a/client/src/lib.rs b/client/src/lib.rs index 14d051a333..8efadb48a4 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,11 +25,11 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientCharacterScreenMsg, ClientGeneralMsg, - ClientInGameMsg, ClientIngame, ClientRegisterMsg, ClientType, DisconnectReason, + validate_chat_msg, ChatMsgValidationError, ClientCharacterScreen, ClientGeneral, + ClientInGame, ClientIngame, ClientMsg, ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, - ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, - ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, + ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, recipe::RecipeBook, @@ -71,7 +71,7 @@ pub enum Event { pub struct Client { registered: bool, - client_ingame: Option, + in_game: Option, thread_pool: ThreadPool, pub server_info: ServerInfo, /// Just the "base" layer for LOD; currently includes colors and nothing @@ -193,7 +193,7 @@ impl Client { max_group_size, client_timeout, ) = match block_on(register_stream.recv())? { - ServerInitMsg::GameSync { + ServerInit::GameSync { entity_package, time_of_day, max_group_size, @@ -362,7 +362,7 @@ impl Client { client_timeout, )) }, - ServerInitMsg::TooManyPlayers => Err(Error::TooManyPlayers), + ServerInit::TooManyPlayers => Err(Error::TooManyPlayers), }?; ping_stream.send(PingMsg::Ping)?; @@ -376,7 +376,7 @@ impl Client { Ok(Self { registered: false, - client_ingame: None, + in_game: None, thread_pool, server_info, world_map, @@ -444,10 +444,9 @@ impl Client { } ).unwrap_or(Ok(username))?; - self.register_stream - .send(ClientRegisterMsg { token_or_username })?; + self.send_msg_err(ClientRegister { token_or_username })?; - match block_on(self.register_stream.recv::())? { + match block_on(self.register_stream.recv::())? { Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn), Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)), Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter), @@ -460,14 +459,69 @@ impl Client { } } + fn send_msg_err(&mut self, msg: S) -> Result<(), network::StreamError> + where + S: Into, + { + let msg: ClientMsg = msg.into(); + #[cfg(debug_assertions)] + { + //There assertions veriy that the state is correct when a msg is send! + match &msg { + ClientMsg::Type(_) | ClientMsg::Register(_) => assert!( + !self.registered, + "must not send msg when already registered" + ), + ClientMsg::CharacterScreen(_) => { + assert!( + self.registered, + "must not send character_screen msg when not registered" + ); + assert!( + self.in_game.is_none(), + "must not send character_screen msg when not in character screen" + ); + }, + ClientMsg::InGame(_) => assert!( + self.in_game.is_some(), + "must not send in_game msg when not in game" + ), + ClientMsg::General(_) => assert!( + self.registered, + "must not send general msg when not registered" + ), + ClientMsg::Ping(_) => (), + } + } + match msg { + ClientMsg::Type(msg) => self.register_stream.send(msg), + ClientMsg::Register(msg) => self.register_stream.send(msg), + ClientMsg::CharacterScreen(msg) => self.character_screen_stream.send(msg), + ClientMsg::InGame(msg) => self.in_game_stream.send(msg), + ClientMsg::General(msg) => self.general_stream.send(msg), + ClientMsg::Ping(msg) => self.ping_stream.send(msg), + } + } + + fn send_msg(&mut self, msg: S) + where + S: Into, + { + let res = self.send_msg_err(msg); + if let Err(e) = res { + warn!( + ?e, + "connection to server no longer possible, couldn't send msg" + ); + } + } + /// Request a state transition to `ClientState::Character`. pub fn request_character(&mut self, character_id: CharacterId) { - self.character_screen_stream - .send(ClientCharacterScreenMsg::Character(character_id)) - .unwrap(); + self.send_msg(ClientCharacterScreen::Character(character_id)); //Assume we are in_game unless server tells us otherwise - self.client_ingame = Some(ClientIngame::Character); + self.in_game = Some(ClientIngame::Character); self.active_character_id = Some(character_id); } @@ -475,87 +529,59 @@ impl Client { /// Load the current players character list pub fn load_character_list(&mut self) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::RequestCharacterList) - .unwrap(); + self.send_msg(ClientCharacterScreen::RequestCharacterList); } /// New character creation pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::CreateCharacter { alias, tool, body }) - .unwrap(); + self.send_msg(ClientCharacterScreen::CreateCharacter { alias, tool, body }); } /// Character deletion pub fn delete_character(&mut self, character_id: CharacterId) { self.character_list.loading = true; - self.character_screen_stream - .send(ClientCharacterScreenMsg::DeleteCharacter(character_id)) - .unwrap(); + self.send_msg(ClientCharacterScreen::DeleteCharacter(character_id)); } /// Send disconnect message to the server pub fn request_logout(&mut self) { debug!("Requesting logout from server"); - if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { - error!( - ?e, - "Couldn't send disconnect package to server, did server close already?" - ); - } + self.send_msg(ClientGeneral::Disconnect); } /// Request a state transition to `ClientState::Registered` from an ingame /// state. - pub fn request_remove_character(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ExitInGame) - .unwrap(); - } + pub fn request_remove_character(&mut self) { self.send_msg(ClientInGame::ExitInGame); } pub fn set_view_distance(&mut self, view_distance: u32) { self.view_distance = Some(view_distance.max(1).min(65)); - self.in_game_stream - .send(ClientInGameMsg::SetViewDistance( - self.view_distance.unwrap(), - )) - .unwrap(); - // Can't fail + self.send_msg(ClientInGame::SetViewDistance(self.view_distance.unwrap())); } pub fn use_slot(&mut self, slot: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Use(slot), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Use(slot), + ))); } pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Swap(a, b), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Swap(a, b), + ))); } pub fn drop_slot(&mut self, slot: comp::slot::Slot) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Drop(slot), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Drop(slot), + ))); } pub fn pick_up(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Pickup(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Pickup(uid), + ))); } } @@ -573,11 +599,9 @@ impl Client { pub fn craft_recipe(&mut self, recipe: &str) -> bool { if self.can_craft_recipe(recipe) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::CraftRecipe(recipe.to_string()), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::CraftRecipe(recipe.to_string()), + ))); true } else { false @@ -594,15 +618,11 @@ impl Client { } pub fn enable_lantern(&mut self) { - self.singleton_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::EnableLantern)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::EnableLantern)); } pub fn disable_lantern(&mut self) { - self.singleton_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::DisableLantern)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::DisableLantern)); } pub fn max_group_size(&self) -> u32 { self.max_group_size } @@ -620,55 +640,43 @@ impl Client { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } pub fn send_group_invite(&mut self, invitee: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Invite(invitee), - ))) - .unwrap() + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Invite(invitee), + ))) } pub fn accept_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Accept, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Accept, + ))); } pub fn decline_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Decline, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Decline, + ))); } pub fn leave_group(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Leave, - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Leave, + ))); } pub fn kick_from_group(&mut self, uid: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::Kick(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::Kick(uid), + ))); } pub fn assign_group_leader(&mut self, uid: Uid) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip( - GroupManip::AssignLeader(uid), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + GroupManip::AssignLeader(uid), + ))); } pub fn is_mounted(&self) -> bool { @@ -689,17 +697,11 @@ impl Client { pub fn mount(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Mount(uid))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::Mount(uid))); } } - pub fn unmount(&mut self) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Unmount)) - .unwrap(); - } + pub fn unmount(&mut self) { self.send_msg(ClientInGame::ControlEvent(ControlEvent::Unmount)); } pub fn respawn(&mut self) { if self @@ -709,9 +711,7 @@ impl Client { .get(self.entity) .map_or(false, |s| s.is_dead) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::Respawn)) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::Respawn)); } } @@ -808,9 +808,7 @@ impl Client { { controller.actions.push(control_action); } - self.in_game_stream - .send(ClientInGameMsg::ControlAction(control_action)) - .unwrap(); + self.send_msg(ClientInGame::ControlAction(control_action)); } pub fn view_distance(&self) -> Option { self.view_distance } @@ -839,10 +837,7 @@ impl Client { /// Send a chat message to the server. pub fn send_chat(&mut self, message: String) { match validate_chat_msg(&message) { - Ok(()) => self - .general_stream - .send(ClientGeneralMsg::ChatMsg(message)) - .unwrap(), + Ok(()) => self.send_msg(ClientGeneral::ChatMsg(message)), Err(ChatMsgValidationError::TooLong) => tracing::warn!( "Attempted to send a message that's too long (Over {} bytes)", MAX_BYTES_CHAT_MSG @@ -857,23 +852,15 @@ impl Client { } pub fn place_block(&mut self, pos: Vec3, block: Block) { - self.in_game_stream - .send(ClientInGameMsg::PlaceBlock(pos, block)) - .unwrap(); + self.send_msg(ClientInGame::PlaceBlock(pos, block)); } - pub fn remove_block(&mut self, pos: Vec3) { - self.in_game_stream - .send(ClientInGameMsg::BreakBlock(pos)) - .unwrap(); - } + pub fn remove_block(&mut self, pos: Vec3) { self.send_msg(ClientInGame::BreakBlock(pos)); } pub fn collect_block(&mut self, pos: Vec3) { - self.in_game_stream - .send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip( - InventoryManip::Collect(pos), - ))) - .unwrap(); + self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + InventoryManip::Collect(pos), + ))); } /// Execute a single client tick, handle input and update the game state by @@ -905,7 +892,7 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. - if self.client_ingame.is_some() { + if self.in_game.is_some() { if let Err(e) = self .state .ecs() @@ -928,8 +915,7 @@ impl Client { "Couldn't access controller component on client entity" ); } - self.in_game_stream - .send(ClientInGameMsg::ControllerInputs(inputs))?; + self.send_msg_err(ClientInGame::ControllerInputs(inputs))?; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -1033,8 +1019,9 @@ impl Client { if self.state.terrain().get_key(*key).is_none() { if !skip_mode && !self.pending_chunks.contains_key(key) { if self.pending_chunks.len() < 4 { - self.in_game_stream - .send(ClientInGameMsg::TerrainChunkRequest { key: *key })?; + self.send_msg_err(ClientInGame::TerrainChunkRequest { + key: *key, + })?; self.pending_chunks.insert(*key, Instant::now()); } else { skip_mode = true; @@ -1066,19 +1053,19 @@ impl Client { // Send a ping to the server once every second if self.state.get_time() - self.last_server_ping > 1. { - self.ping_stream.send(PingMsg::Ping)?; + self.send_msg_err(PingMsg::Ping)?; self.last_server_ping = self.state.get_time(); } // 6) Update the server about the player's physics attributes. - if self.client_ingame.is_some() { + if self.in_game.is_some() { if let (Some(pos), Some(vel), Some(ori)) = ( self.state.read_storage().get(self.entity).cloned(), self.state.read_storage().get(self.entity).cloned(), self.state.read_storage().get(self.entity).cloned(), ) { self.in_game_stream - .send(ClientInGameMsg::PlayerPhysics { pos, vel, ori })?; + .send(ClientInGame::PlayerPhysics { pos, vel, ori })?; } } @@ -1108,26 +1095,26 @@ impl Client { fn handle_server_msg( &mut self, frontend_events: &mut Vec, - msg: ServerGeneralMsg, + msg: ServerGeneral, ) -> Result<(), Error> { match msg { - ServerGeneralMsg::Disconnect(reason) => match reason { + ServerGeneral::Disconnect(reason) => match reason { DisconnectReason::Shutdown => return Err(Error::ServerShutdown), DisconnectReason::Requested => { debug!("finally sending ClientMsg::Terminate"); frontend_events.push(Event::Disconnect); - self.general_stream.send(ClientGeneralMsg::Terminate)?; + self.send_msg_err(ClientGeneral::Terminate)?; }, DisconnectReason::Kicked(reason) => { debug!("sending ClientMsg::Terminate because we got kicked"); frontend_events.push(Event::Kicked(reason)); - self.general_stream.send(ClientGeneralMsg::Terminate)?; + self.send_msg_err(ClientGeneral::Terminate)?; }, }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(list)) => { self.player_list = list }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => { if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) { warn!( "Received msg to insert {} with uid {} into the player list but there was \ @@ -1136,7 +1123,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.is_admin = admin; } else { @@ -1147,7 +1134,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( + ServerGeneral::PlayerListUpdate(PlayerListUpdate::SelectedCharacter( uid, char_info, )) => { @@ -1161,7 +1148,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.character = match &player_info.character { Some(character) => Some(common::msg::CharacterInfo { @@ -1180,7 +1167,7 @@ impl Client { }; } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => { // Instead of removing players, mark them as offline because we need to // remember the names of disconnected players in chat. // @@ -1205,7 +1192,7 @@ impl Client { ); } }, - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => { if let Some(player_info) = self.player_list.get_mut(&uid) { player_info.player_alias = new_name; } else { @@ -1216,38 +1203,38 @@ impl Client { ); } }, - ServerGeneralMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), - ServerGeneralMsg::SetPlayerEntity(uid) => { + ServerGeneral::ChatMsg(m) => frontend_events.push(Event::Chat(m)), + ServerGeneral::SetPlayerEntity(uid) => { if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { self.entity = entity; } else { return Err(Error::Other("Failed to find entity from uid.".to_owned())); } }, - ServerGeneralMsg::TimeOfDay(time_of_day) => { + ServerGeneral::TimeOfDay(time_of_day) => { *self.state.ecs_mut().write_resource() = time_of_day; }, - ServerGeneralMsg::EntitySync(entity_sync_package) => { + ServerGeneral::EntitySync(entity_sync_package) => { self.state .ecs_mut() .apply_entity_sync_package(entity_sync_package); }, - ServerGeneralMsg::CompSync(comp_sync_package) => { + ServerGeneral::CompSync(comp_sync_package) => { self.state .ecs_mut() .apply_comp_sync_package(comp_sync_package); }, - ServerGeneralMsg::CreateEntity(entity_package) => { + ServerGeneral::CreateEntity(entity_package) => { self.state.ecs_mut().apply_entity_package(entity_package); }, - ServerGeneralMsg::DeleteEntity(entity) => { + ServerGeneral::DeleteEntity(entity) => { if self.uid() != Some(entity) { self.state .ecs_mut() .delete_entity_and_clear_from_uid_allocator(entity.0); } }, - ServerGeneralMsg::Notification(n) => { + ServerGeneral::Notification(n) => { frontend_events.push(Event::Notification(n)); }, } @@ -1257,10 +1244,10 @@ impl Client { fn handle_server_in_game_msg( &mut self, frontend_events: &mut Vec, - msg: ServerInGameMsg, + msg: ServerInGame, ) -> Result<(), Error> { match msg { - ServerInGameMsg::GroupUpdate(change_notification) => { + ServerInGame::GroupUpdate(change_notification) => { use comp::group::ChangeNotification::*; // Note: we use a hashmap since this would not work with entities outside // the view distance @@ -1332,15 +1319,15 @@ impl Client { }, } }, - ServerInGameMsg::GroupInvite { inviter, timeout } => { + ServerInGame::GroupInvite { inviter, timeout } => { self.group_invite = Some((inviter, std::time::Instant::now(), timeout)); }, - ServerInGameMsg::InvitePending(uid) => { + ServerInGame::InvitePending(uid) => { if !self.pending_invites.insert(uid) { warn!("Received message about pending invite that was already pending"); } }, - ServerInGameMsg::InviteComplete { target, answer } => { + ServerInGame::InviteComplete { target, answer } => { if !self.pending_invites.remove(&target) { warn!( "Received completed invite message for invite that was not in the list of \ @@ -1358,11 +1345,11 @@ impl Client { frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg))); }, // Cleanup for when the client goes back to the `in_game = None` - ServerInGameMsg::ExitInGameSuccess => { - self.client_ingame = None; + ServerInGame::ExitInGameSuccess => { + self.in_game = None; self.clean_state(); }, - ServerInGameMsg::InventoryUpdate(mut inventory, event) => { + ServerInGame::InventoryUpdate(mut inventory, event) => { match event { InventoryUpdateEvent::CollectFailed => {}, _ => { @@ -1376,25 +1363,25 @@ impl Client { frontend_events.push(Event::InventoryUpdated(event)); }, - ServerInGameMsg::TerrainChunkUpdate { key, chunk } => { + ServerInGame::TerrainChunkUpdate { key, chunk } => { if let Ok(chunk) = chunk { self.state.insert_chunk(key, *chunk); } self.pending_chunks.remove(&key); }, - ServerInGameMsg::TerrainBlockUpdates(mut blocks) => { + ServerInGame::TerrainBlockUpdates(mut blocks) => { blocks.drain().for_each(|(pos, block)| { self.state.set_block(pos, block); }); }, - ServerInGameMsg::SetViewDistance(vd) => { + ServerInGame::SetViewDistance(vd) => { self.view_distance = Some(vd); frontend_events.push(Event::SetViewDistance(vd)); }, - ServerInGameMsg::Outcomes(outcomes) => { + ServerInGame::Outcomes(outcomes) => { frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) }, - ServerInGameMsg::Knockback(impulse) => { + ServerInGame::Knockback(impulse) => { self.state .ecs() .read_resource::>() @@ -1409,24 +1396,24 @@ impl Client { fn handle_server_character_screen_msg( &mut self, - msg: ServerCharacterScreenMsg, + msg: ServerCharacterScreen, ) -> Result<(), Error> { match msg { - ServerCharacterScreenMsg::CharacterListUpdate(character_list) => { + ServerCharacterScreen::CharacterListUpdate(character_list) => { self.character_list.characters = character_list; self.character_list.loading = false; }, - ServerCharacterScreenMsg::CharacterActionError(error) => { + ServerCharacterScreen::CharacterActionError(error) => { warn!("CharacterActionError: {:?}.", error); self.character_list.error = Some(error); }, - ServerCharacterScreenMsg::CharacterDataLoadError(error) => { + ServerCharacterScreen::CharacterDataLoadError(error) => { trace!("Handling join error by server"); - self.client_ingame = None; + self.in_game = None; self.clean_state(); self.character_list.error = Some(error); }, - ServerCharacterScreenMsg::CharacterSuccess => { + ServerCharacterScreen::CharacterSuccess => { debug!("client is now in ingame state on server"); if let Some(vd) = self.view_distance { self.set_view_distance(vd); @@ -1439,7 +1426,7 @@ impl Client { fn handle_ping_msg(&mut self, msg: PingMsg) -> Result<(), Error> { match msg { PingMsg::Ping => { - self.ping_stream.send(PingMsg::Pong)?; + self.send_msg_err(PingMsg::Pong)?; }, PingMsg::Pong => { self.last_server_pong = self.state.get_time(); @@ -1535,7 +1522,7 @@ impl Client { pub fn get_client_type(&self) -> ClientType { ClientType::Game } - pub fn get_in_game(&self) -> Option { self.client_ingame } + pub fn get_in_game(&self) -> Option { self.in_game } pub fn get_registered(&self) -> bool { self.registered } @@ -1810,12 +1797,16 @@ impl Client { impl Drop for Client { fn drop(&mut self) { trace!("Dropping client"); - if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) { - warn!( - ?e, - "Error during drop of client, couldn't send disconnect package, is the connection \ - already closed?", - ); + if self.registered { + if let Err(e) = self.send_msg_err(ClientGeneral::Disconnect) { + warn!( + ?e, + "Error during drop of client, couldn't send disconnect package, is the \ + connection already closed?", + ); + } + } else { + trace!("no disconnect msg necessary as client wasn't registered") } if let Err(e) = block_on(self.participant.take().unwrap().disconnect()) { warn!(?e, "error when disconnecting, couldn't send all data"); diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 0888a9e21b..4774b18683 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -1,4 +1,4 @@ -use crate::{comp::group::Group, msg::ServerGeneralMsg, sync::Uid}; +use crate::{comp::group::Group, msg::ServerGeneral, sync::Uid}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -118,11 +118,11 @@ impl ChatType { } } impl ChatType { - pub fn server_msg(self, msg: S) -> ServerGeneralMsg + pub fn server_msg(self, msg: S) -> ServerGeneral where S: Into, { - ServerGeneralMsg::ChatMsg(self.chat_msg(msg)) + ServerGeneral::ChatMsg(self.chat_msg(msg)) } } // Stores chat text, type diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 5e17c978ab..2339bcab12 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,3 +1,4 @@ +use super::PingMsg; use crate::{ character::CharacterId, comp, @@ -7,25 +8,48 @@ use crate::{ use serde::{Deserialize, Serialize}; use vek::*; +///This struct contains all messages the client might send (on different +/// streams though). It's used to verify the correctness of the state in +/// debug_assertions +#[derive(Debug, Clone)] +pub enum ClientMsg { + ///Send on the first connection ONCE to identify client intention for + /// server + Type(ClientType), + ///Send ONCE to register/auth to the server + Register(ClientRegister), + ///Msg only to send while in character screen, e.g. `CreateCharacter` + CharacterScreen(ClientCharacterScreen), + ///Msg only to send while playing in game, e.g. `PlayerPositionUpdates` + InGame(ClientInGame), + ///Msg that can be send ALWAYS as soon as we are registered, e.g. `Chat` + General(ClientGeneral), + Ping(PingMsg), +} + +/* +2nd Level Enums +*/ + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ClientType { - // Regular Client like Voxygen who plays the game + /// Regular Client like Voxygen who plays the game Game, - // A Chatonly client, which doesn't want to connect via its character + /// A Chatonly client, which doesn't want to connect via its character ChatOnly, - // A unprivileged bot, e.g. to request world information - // Or a privileged bot, e.g. to run admin commands used by server-cli + /// A unprivileged bot, e.g. to request world information + /// Or a privileged bot, e.g. to run admin commands used by server-cli Bot { privileged: bool }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ClientRegisterMsg { +pub struct ClientRegister { pub token_or_username: String, } //messages send by clients only valid when in character screen #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientCharacterScreenMsg { +pub enum ClientCharacterScreen { RequestCharacterList, CreateCharacter { alias: String, @@ -39,7 +63,7 @@ pub enum ClientCharacterScreenMsg { //messages send by clients only valid when in game (with a character) #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientInGameMsg { +pub enum ClientInGame { ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), ControlAction(comp::ControlAction), @@ -62,8 +86,36 @@ pub enum ClientInGameMsg { /// Messages sent from the client to the server #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClientGeneralMsg { +pub enum ClientGeneral { ChatMsg(String), Disconnect, Terminate, } + +/* +end of 2nd level Enums +*/ + +impl Into for ClientType { + fn into(self) -> ClientMsg { ClientMsg::Type(self) } +} + +impl Into for ClientRegister { + fn into(self) -> ClientMsg { ClientMsg::Register(self) } +} + +impl Into for ClientCharacterScreen { + fn into(self) -> ClientMsg { ClientMsg::CharacterScreen(self) } +} + +impl Into for ClientInGame { + fn into(self) -> ClientMsg { ClientMsg::InGame(self) } +} + +impl Into for ClientGeneral { + fn into(self) -> ClientMsg { ClientMsg::General(self) } +} + +impl Into for PingMsg { + fn into(self) -> ClientMsg { ClientMsg::Ping(self) } +} diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 58367669d3..b4ea9fe6fe 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -1,18 +1,20 @@ pub mod client; pub mod ecs_packet; pub mod server; +pub mod world_packet; // Reexports pub use self::{ client::{ - ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientRegisterMsg, ClientType, + ClientCharacterScreen, ClientGeneral, ClientInGame, ClientMsg, ClientRegister, ClientType, }, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, - ServerInitMsg, ServerRegisterAnswerMsg, + RegisterError, ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, + ServerMsg, ServerRegisterAnswer, }, + world_packet::WorldMapMsg, }; use serde::{Deserialize, Serialize}; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 59d2b0b91d..82b861ef6e 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,4 +1,4 @@ -use super::EcsCompPacket; +use super::{EcsCompPacket, PingMsg}; use crate::{ character::CharacterItem, comp, @@ -14,6 +14,31 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use vek::*; +///This struct contains all messages the server might send (on different +/// streams though) +#[derive(Debug, Clone)] +pub enum ServerMsg { + /// Basic info about server, send ONCE, clients need it to Register + Info(ServerInfo), + /// Initial data package, send BEFORE Register ONCE. Not Register relevant + Init(ServerInit), + /// Result to `ClientMsg::Register`. send ONCE + RegisterAnswer(ServerRegisterAnswer), + /// Msg only to send when client is on the character screen, e.g. + /// `CharacterListUpdate` + CharacterScreen(ServerCharacterScreen), + /// Msg only to send when client is playing in game, e.g. + /// `TerrainChunkUpdate` + InGame(ServerInGame), + ///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat` + General(ServerGeneral), + Ping(PingMsg), +} + +/* +2nd Level Enums +*/ + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerInfo { pub name: String, @@ -23,195 +48,26 @@ pub struct ServerInfo { pub auth_provider: Option, } -/// Inform the client of updates to the player list. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PlayerListUpdate { - Init(HashMap), - Add(Uid, PlayerInfo), - SelectedCharacter(Uid, CharacterInfo), - LevelChange(Uid, u32), - Admin(Uid, bool), - Remove(Uid), - Alias(Uid, String), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PlayerInfo { - pub is_admin: bool, - pub is_online: bool, - pub player_alias: String, - pub character: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CharacterInfo { - pub name: String, - pub level: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -/// World map information. Note that currently, we always send the whole thing -/// in one go, but the structure aims to try to provide information as locally -/// as possible, so that in the future we can split up large maps into multiple -/// WorldMapMsg fragments. -/// -/// TODO: Update message format to make fragmentable, allowing us to send more -/// information without running into bandwidth issues. -/// -/// TODO: Add information for rivers (currently, we just prerender them on the -/// server, but this is not a great solution for LoD. The map rendering code is -/// already set up to be able to take advantage of the river rendering being -/// split out, but the format is a little complicated for space reasons and it -/// may take some tweaking to get right, so we avoid sending it for now). -/// -/// TODO: measure explicit compression schemes that might save space, e.g. -/// repeating the "small angles" optimization that works well on more detailed -/// shadow maps intended for height maps. -pub struct WorldMapMsg { - /// Log base 2 of world map dimensions (width × height) in chunks. - /// - /// NOTE: Invariant: chunk count fits in a u16. - pub dimensions_lg: Vec2, - /// Sea level (used to provide a base altitude). - pub sea_level: f32, - /// Max height (used to scale altitudes). - pub max_height: f32, - /// RGB+A; the alpha channel is currently unused, but will be used in the - /// future. Entries are in the usual chunk order. - pub rgba: Vec, - /// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for - /// altitude. The remainder are currently unused, but we have plans to - /// use 7 bits for water depth (using an integer f7 encoding), and we - /// will find other uses for the remaining 12 bits. - pub alt: Vec, - /// Horizon mapping. This is a variant of shadow mapping that is - /// specifically designed for height maps; it takes advantage of their - /// regular structure (e.g. no holes) to compress all information needed - /// to decide when to cast a sharp shadow into a single nagle, the "horizon - /// angle." This is the smallest angle with the ground at which light can - /// pass through any occluders to reach the chunk, in some chosen - /// horizontal direction. This would not be sufficient for a more - /// complicated 3D structure, but it works for height maps since: - /// - /// 1. they have no gaps, so as soon as light can shine through it will - /// always be able to do so, and - /// 2. we only care about lighting from the top, and only from the east and - /// west (since at a large scale like this we mostly just want to - /// handle variable sunlight; moonlight would present more challenges - /// but we currently have no plans to try to cast accurate shadows in - /// moonlight). - /// - /// Our chosen format is two pairs of vectors, - /// with the first pair representing west-facing light (casting shadows on - /// the left side) and the second representing east-facing light - /// (casting shadows on the east side). - /// - /// The pair of vectors consists of (with each vector in the usual chunk - /// order): - /// - /// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360). - /// We might consider switching to tangent if that represents the - /// information we care about better. - /// * Approximate (floor) height of maximal occluder. We currently use this - /// to try to deliver some approximation of soft shadows, which isn't that - /// big a deal on the world map but is probably needed in order to ensure - /// smooth transitions between chunks in LoD view. Additionally, when we - /// start using the shadow information to do local lighting on the world - /// map, we'll want a quick way to test where we can go out of shadow at - /// arbitrary heights (since the player and other entities cajn find - /// themselves far from the ground at times). While this is only an - /// approximation to a proper distance map, hopefully it will give us - /// something that feels reasonable enough for Veloren's style. - /// - /// NOTE: On compression. - /// - /// Horizon mapping has a lot of advantages for height maps (simple, easy to - /// understand, doesn't require any fancy math or approximation beyond - /// precision loss), though it loses a few of them by having to store - /// distance to occluder as well. However, just storing tons - /// and tons of regular shadow maps (153 for a full day cycle, stored at - /// irregular intervals) combined with clever explicit compression and - /// avoiding recording sharp local shadows (preferring retracing for - /// these), yielded a compression rate of under 3 bits per column! Since - /// we likely want to avoid per-column shadows for worlds of the sizes we - /// want, we'd still need to store *some* extra information to create - /// soft shadows, but it would still be nice to try to drive down our - /// size as much as possible given how compressible shadows of height - /// maps seem to be in practice. Therefore, we try to take advantage of the - /// way existing compression algorithms tend to work to see if we can - /// achieve significant gains without doing a lot of custom work. - /// - /// Specifically, since our rays are cast east/west, we expect that for each - /// row, the horizon angles in each direction should be sequences of - /// monotonically increasing values (as chunks approach a tall - /// occluder), followed by sequences of no shadow, repeated - /// until the end of the map. Monotonic sequences and same-byte sequences - /// are usually easy to compress and existing algorithms are more likely - /// to be able to deal with them than jumbled data. If we were to keep - /// both directions in the same vector, off-the-shelf compression would - /// probably be less effective. - /// - /// For related reasons, rather than storing distances as in a standard - /// distance map (which would lead to monotonically *decreasing* values - /// as we approached the occluder from a given direction), we store the - /// estimated *occluder height.* The idea here is that we replace the - /// monotonic sequences with constant sequences, which are extremely - /// straightforward to compress and mostly handled automatically by anything - /// that does run-length encoding (i.e. most off-the-shelf compression - /// algorithms). - /// - /// We still need to benchmark this properly, as there's no guarantee our - /// current compression algorithms will actually work well on this data - /// in practice. It's possible that some other permutation (e.g. more - /// bits reserved for "distance to occluder" in exchange for an even - /// more predictible sequence) would end up compressing better than storing - /// angles, or that we don't need as much precision as we currently have - /// (256 possible angles). - pub horizons: [(Vec, Vec); 2], -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum InviteAnswer { - Accepted, - Declined, - TimedOut, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Notification { - WaypointSaved, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DisconnectReason { - /// Server shut down - Shutdown, - /// Client sent disconnect message - Requested, - /// Client was kicked - Kicked(String), -} - /// Reponse To ClientType #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(clippy::clippy::large_enum_variant)] -pub enum ServerInitMsg { +pub enum ServerInit { TooManyPlayers, GameSync { entity_package: sync::EntityPackage, time_of_day: state::TimeOfDay, max_group_size: u32, client_timeout: Duration, - world_map: WorldMapMsg, + world_map: crate::msg::world_packet::WorldMapMsg, recipe_book: RecipeBook, }, } -pub type ServerRegisterAnswerMsg = Result<(), RegisterError>; +pub type ServerRegisterAnswer = Result<(), RegisterError>; //Messages only allowed while client in character screen #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerCharacterScreenMsg { +pub enum ServerCharacterScreen { /// An error occurred while loading character data CharacterDataLoadError(String), /// A list of characters belonging to the a authenticated player was sent @@ -223,7 +79,7 @@ pub enum ServerCharacterScreenMsg { //Messages only allowed while client is in game (with a character) #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerInGameMsg { +pub enum ServerInGame { GroupUpdate(comp::group::ChangeNotification), // Indicate to the client that they are invited to join a group GroupInvite { @@ -256,7 +112,7 @@ pub enum ServerInGameMsg { /// Messages sent from the server to the client #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerGeneralMsg { +pub enum ServerGeneral { PlayerListUpdate(PlayerListUpdate), /// A message to go into the client chat box. The client is responsible for /// formatting the message and turning it into a speech bubble. @@ -272,6 +128,58 @@ pub enum ServerGeneralMsg { Notification(Notification), } +/* +end of 2nd level Enums +*/ + +/// Inform the client of updates to the player list. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PlayerListUpdate { + Init(HashMap), + Add(Uid, PlayerInfo), + SelectedCharacter(Uid, CharacterInfo), + LevelChange(Uid, u32), + Admin(Uid, bool), + Remove(Uid), + Alias(Uid, String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlayerInfo { + pub is_admin: bool, + pub is_online: bool, + pub player_alias: String, + pub character: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CharacterInfo { + pub name: String, + pub level: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum InviteAnswer { + Accepted, + Declined, + TimedOut, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Notification { + WaypointSaved, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DisconnectReason { + /// Server shut down + Shutdown, + /// Client sent disconnect message + Requested, + /// Client was kicked + Kicked(String), +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum RegisterError { AlreadyLoggedIn, @@ -286,6 +194,34 @@ impl From for RegisterError { fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) } } -impl From for ServerGeneralMsg { - fn from(v: comp::ChatMsg) -> Self { ServerGeneralMsg::ChatMsg(v) } +impl From for ServerGeneral { + fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) } +} + +impl Into for ServerInfo { + fn into(self) -> ServerMsg { ServerMsg::Info(self) } +} + +impl Into for ServerInit { + fn into(self) -> ServerMsg { ServerMsg::Init(self) } +} + +impl Into for ServerRegisterAnswer { + fn into(self) -> ServerMsg { ServerMsg::RegisterAnswer(self) } +} + +impl Into for ServerCharacterScreen { + fn into(self) -> ServerMsg { ServerMsg::CharacterScreen(self) } +} + +impl Into for ServerInGame { + fn into(self) -> ServerMsg { ServerMsg::InGame(self) } +} + +impl Into for ServerGeneral { + fn into(self) -> ServerMsg { ServerMsg::General(self) } +} + +impl Into for PingMsg { + fn into(self) -> ServerMsg { ServerMsg::Ping(self) } } diff --git a/common/src/msg/world_packet.rs b/common/src/msg/world_packet.rs new file mode 100644 index 0000000000..c19c855166 --- /dev/null +++ b/common/src/msg/world_packet.rs @@ -0,0 +1,123 @@ +use serde::{Deserialize, Serialize}; +use vek::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// World map information. Note that currently, we always send the whole thing +/// in one go, but the structure aims to try to provide information as locally +/// as possible, so that in the future we can split up large maps into multiple +/// WorldMapMsg fragments. +/// +/// TODO: Update message format to make fragmentable, allowing us to send more +/// information without running into bandwidth issues. +/// +/// TODO: Add information for rivers (currently, we just prerender them on the +/// server, but this is not a great solution for LoD. The map rendering code is +/// already set up to be able to take advantage of the river rendering being +/// split out, but the format is a little complicated for space reasons and it +/// may take some tweaking to get right, so we avoid sending it for now). +/// +/// TODO: measure explicit compression schemes that might save space, e.g. +/// repeating the "small angles" optimization that works well on more detailed +/// shadow maps intended for height maps. +pub struct WorldMapMsg { + /// Log base 2 of world map dimensions (width × height) in chunks. + /// + /// NOTE: Invariant: chunk count fits in a u16. + pub dimensions_lg: Vec2, + /// Sea level (used to provide a base altitude). + pub sea_level: f32, + /// Max height (used to scale altitudes). + pub max_height: f32, + /// RGB+A; the alpha channel is currently unused, but will be used in the + /// future. Entries are in the usual chunk order. + pub rgba: Vec, + /// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for + /// altitude. The remainder are currently unused, but we have plans to + /// use 7 bits for water depth (using an integer f7 encoding), and we + /// will find other uses for the remaining 12 bits. + pub alt: Vec, + /// Horizon mapping. This is a variant of shadow mapping that is + /// specifically designed for height maps; it takes advantage of their + /// regular structure (e.g. no holes) to compress all information needed + /// to decide when to cast a sharp shadow into a single nagle, the "horizon + /// angle." This is the smallest angle with the ground at which light can + /// pass through any occluders to reach the chunk, in some chosen + /// horizontal direction. This would not be sufficient for a more + /// complicated 3D structure, but it works for height maps since: + /// + /// 1. they have no gaps, so as soon as light can shine through it will + /// always be able to do so, and + /// 2. we only care about lighting from the top, and only from the east and + /// west (since at a large scale like this we mostly just want to + /// handle variable sunlight; moonlight would present more challenges + /// but we currently have no plans to try to cast accurate shadows in + /// moonlight). + /// + /// Our chosen format is two pairs of vectors, + /// with the first pair representing west-facing light (casting shadows on + /// the left side) and the second representing east-facing light + /// (casting shadows on the east side). + /// + /// The pair of vectors consists of (with each vector in the usual chunk + /// order): + /// + /// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360). + /// We might consider switching to tangent if that represents the + /// information we care about better. + /// * Approximate (floor) height of maximal occluder. We currently use this + /// to try to deliver some approximation of soft shadows, which isn't that + /// big a deal on the world map but is probably needed in order to ensure + /// smooth transitions between chunks in LoD view. Additionally, when we + /// start using the shadow information to do local lighting on the world + /// map, we'll want a quick way to test where we can go out of shadow at + /// arbitrary heights (since the player and other entities cajn find + /// themselves far from the ground at times). While this is only an + /// approximation to a proper distance map, hopefully it will give us + /// something that feels reasonable enough for Veloren's style. + /// + /// NOTE: On compression. + /// + /// Horizon mapping has a lot of advantages for height maps (simple, easy to + /// understand, doesn't require any fancy math or approximation beyond + /// precision loss), though it loses a few of them by having to store + /// distance to occluder as well. However, just storing tons + /// and tons of regular shadow maps (153 for a full day cycle, stored at + /// irregular intervals) combined with clever explicit compression and + /// avoiding recording sharp local shadows (preferring retracing for + /// these), yielded a compression rate of under 3 bits per column! Since + /// we likely want to avoid per-column shadows for worlds of the sizes we + /// want, we'd still need to store *some* extra information to create + /// soft shadows, but it would still be nice to try to drive down our + /// size as much as possible given how compressible shadows of height + /// maps seem to be in practice. Therefore, we try to take advantage of the + /// way existing compression algorithms tend to work to see if we can + /// achieve significant gains without doing a lot of custom work. + /// + /// Specifically, since our rays are cast east/west, we expect that for each + /// row, the horizon angles in each direction should be sequences of + /// monotonically increasing values (as chunks approach a tall + /// occluder), followed by sequences of no shadow, repeated + /// until the end of the map. Monotonic sequences and same-byte sequences + /// are usually easy to compress and existing algorithms are more likely + /// to be able to deal with them than jumbled data. If we were to keep + /// both directions in the same vector, off-the-shelf compression would + /// probably be less effective. + /// + /// For related reasons, rather than storing distances as in a standard + /// distance map (which would lead to monotonically *decreasing* values + /// as we approached the occluder from a given direction), we store the + /// estimated *occluder height.* The idea here is that we replace the + /// monotonic sequences with constant sequences, which are extremely + /// straightforward to compress and mostly handled automatically by anything + /// that does run-length encoding (i.e. most off-the-shelf compression + /// algorithms). + /// + /// We still need to benchmark this properly, as there's no guarantee our + /// current compression algorithms will actually work well on this data + /// in practice. It's possible that some other permutation (e.g. more + /// bits reserved for "distance to occluder" in exchange for an even + /// more predictible sequence) would end up compressing better than storing + /// angles, or that we don't need as much precision as we currently have + /// (256 possible angles). + pub horizons: [(Vec, Vec); 2], +} diff --git a/server/src/client.rs b/server/src/client.rs index a473c03c52..f3db6ee99e 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,16 +1,16 @@ use crate::error::Error; use common::msg::{ - ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientType, PingMsg, - ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInitMsg, + ClientCharacterScreen, ClientGeneral, ClientInGame, ClientIngame, ClientType, PingMsg, + ServerMsg, }; use hashbrown::HashSet; -use network::{MessageBuffer, Participant, Stream}; +use network::{Participant, Stream}; use serde::{de::DeserializeOwned, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; use std::sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Mutex, }; use tracing::debug; use vek::*; @@ -20,7 +20,7 @@ pub struct Client { pub client_type: ClientType, pub in_game: Option, pub participant: Mutex>, - pub singleton_stream: Stream, + pub general_stream: Stream, pub ping_stream: Stream, pub register_stream: Stream, pub character_screen_stream: Stream, @@ -44,6 +44,7 @@ impl Client { } } + /* fn internal_send_raw(b: &AtomicBool, s: &mut Stream, msg: Arc) { if !b.load(Ordering::Relaxed) { if let Err(e) = s.send_raw(msg) { @@ -52,29 +53,32 @@ impl Client { } } } + */ - pub fn send_init(&mut self, msg: ServerInitMsg) { - Self::internal_send(&self.network_error, &mut self.register_stream, msg); - } - - pub fn send_msg(&mut self, msg: ServerGeneralMsg) { - Self::internal_send(&self.network_error, &mut self.singleton_stream, msg); - } - - pub fn send_in_game(&mut self, msg: ServerInGameMsg) { - Self::internal_send(&self.network_error, &mut self.in_game_stream, msg); - } - - pub fn send_character_screen(&mut self, msg: ServerCharacterScreenMsg) { - Self::internal_send(&self.network_error, &mut self.character_screen_stream, msg); - } - - pub fn send_ping(&mut self, msg: PingMsg) { - Self::internal_send(&self.network_error, &mut self.ping_stream, msg); - } - - pub fn send_msg_raw(&mut self, msg: Arc) { - Self::internal_send_raw(&self.network_error, &mut self.singleton_stream, msg); + pub fn send_msg(&mut self, msg: S) + where + S: Into, + { + const ERR: &str = "Dont do that, thats only done once at the start, no via this class"; + match msg.into() { + ServerMsg::Info(_) => panic!(ERR), + ServerMsg::Init(_) => panic!(ERR), + ServerMsg::RegisterAnswer(msg) => { + Self::internal_send(&self.network_error, &mut self.register_stream, &msg) + }, + ServerMsg::CharacterScreen(msg) => { + Self::internal_send(&self.network_error, &mut self.character_screen_stream, &msg) + }, + ServerMsg::InGame(msg) => { + Self::internal_send(&self.network_error, &mut self.in_game_stream, &msg) + }, + ServerMsg::General(msg) => { + Self::internal_send(&self.network_error, &mut self.general_stream, &msg) + }, + ServerMsg::Ping(msg) => { + Self::internal_send(&self.network_error, &mut self.ping_stream, &msg) + }, + }; } pub async fn internal_recv( @@ -95,15 +99,15 @@ impl Client { } } - pub async fn recv_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.singleton_stream).await + pub async fn recv_msg(&mut self) -> Result { + Self::internal_recv(&self.network_error, &mut self.general_stream).await } - pub async fn recv_in_game_msg(&mut self) -> Result { + pub async fn recv_in_game_msg(&mut self) -> Result { Self::internal_recv(&self.network_error, &mut self.in_game_stream).await } - pub async fn recv_character_screen_msg(&mut self) -> Result { + pub async fn recv_character_screen_msg(&mut self) -> Result { Self::internal_recv(&self.network_error, &mut self.character_screen_stream).await } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9f2fa203fc..4d0b61d807 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral, ServerInGame}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -504,7 +504,7 @@ fn handle_alias( ecs.read_storage::().get(target), old_alias_optional, ) { - let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias( + let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias( *uid, player.alias.clone(), )); @@ -669,9 +669,7 @@ fn handle_spawn( .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| { - c.send_in_game(ServerInGameMsg::GroupUpdate(g)) - }); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } else if let Some(group) = match alignment { @@ -1161,7 +1159,7 @@ fn handle_waypoint( server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!")); server.notify_client( client, - ServerGeneralMsg::Notification(Notification::WaypointSaved), + ServerGeneral::Notification(Notification::WaypointSaved), ); }, None => server.notify_client( @@ -1198,7 +1196,7 @@ fn handle_adminify( ecs.write_storage().insert(player, comp::Admin).is_ok() }; // Update player list so the player shows up as admin in client chat. - let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin( + let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin( *ecs.read_storage::() .get(player) .expect("Player should have uid"), @@ -1591,7 +1589,7 @@ fn find_target( ecs: &specs::World, opt_alias: Option, fallback: EcsEntity, -) -> Result { +) -> Result { if let Some(alias) = opt_alias { (&ecs.entities(), &ecs.read_storage::()) .join() @@ -1663,7 +1661,7 @@ fn handle_set_level( .expect("Failed to get uid for player"); server .state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + .notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::LevelChange(uid, lvl), )); @@ -1903,7 +1901,7 @@ fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) { .emit_now(ServerEvent::ClientDisconnect(target_player)); server.notify_client( target_player, - ServerGeneralMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())), + ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())), ); } diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index 4e3db3b09a..a2c7145f58 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -134,7 +134,7 @@ impl ConnectionHandler { client_type, in_game: None, participant: std::sync::Mutex::new(Some(participant)), - singleton_stream: general_stream, + general_stream, ping_stream, register_stream, in_game_stream, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 16eda3ef29..d480302e82 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3) } let mut clients = state.ecs().write_storage::(); if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::Knockback(impulse)); + client.send_msg(ServerInGame::Knockback(impulse)); } } @@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { server .state - .notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + .notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::LevelChange(*uid, new_level), )); } diff --git a/server/src/events/group_manip.rs b/server/src/events/group_manip.rs index 6c96644968..bbaf5c0b32 100644 --- a/server/src/events/group_manip.rs +++ b/server/src/events/group_manip.rs @@ -5,7 +5,7 @@ use common::{ group::{self, Group, GroupManager, Invite, PendingInvites}, ChatType, GroupManip, }, - msg::{InviteAnswer, ServerInGameMsg}, + msg::{InviteAnswer, ServerInGame}, sync, sync::WorldSyncExt, }; @@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani (clients.get_mut(invitee), uids.get(entity).copied()) { if send_invite() { - client.send_in_game(ServerInGameMsg::GroupInvite { + client.send_msg(ServerInGame::GroupInvite { inviter, timeout: PRESENTED_INVITE_TIMEOUT_DUR, }); @@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani // Notify inviter that the invite is pending if invite_sent { if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::InvitePending(uid)); + client.send_msg(ServerInGame::InvitePending(uid)); } } }, @@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::Accepted, }) @@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } @@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::Declined, }) @@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); }, @@ -336,7 +336,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); @@ -410,7 +410,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); // Tell them they are the leader diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 2496c0f7fa..d67ca8a1d4 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -4,7 +4,7 @@ use crate::{ }; use common::{ comp::{self, item}, - msg::ServerGeneralMsg, + msg::ServerGeneral, sync::{Uid, WorldSyncExt}, }; use specs::{world::WorldExt, Entity as EcsEntity}; @@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { let mut clients = ecs.write_storage::(); if clients.get_mut(possesse).is_none() { if let Some(mut client) = clients.remove(possessor) { - client.send_msg(ServerGeneralMsg::SetPlayerEntity(possesse_uid)); + client.send_msg(ServerGeneral::SetPlayerEntity(possesse_uid)); clients .insert(possesse, client) .err() diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index c894479dc8..2f39db3811 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -5,7 +5,7 @@ use common::{ slot::{self, Slot}, Pos, MAX_PICKUP_RANGE_SQR, }, - msg::ServerInGameMsg, + msg::ServerInGame, recipe::default_recipe_book, sync::{Uid, WorldSyncExt}, vol::ReadVol, @@ -281,9 +281,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .map(|g| (g, c)) }) .map(|(g, c)| { - c.send_in_game( - ServerInGameMsg::GroupUpdate(g), - ) + c.send_msg(ServerInGame::GroupUpdate(g)) }); }, ); diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 572c5d9058..db6a164a14 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg}, + msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, span, sync::{Uid, UidAllocator}, }; @@ -34,7 +34,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { // Tell client its request was successful client.in_game = None; - client.send_in_game(ServerInGameMsg::ExitInGameSuccess); + client.send_msg(ServerInGame::ExitInGameSuccess); let entity_builder = state.ecs_mut().create_entity().with(client).with(player); @@ -130,9 +130,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event ) { state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg("")); - state.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( - PlayerListUpdate::Remove(*uid), - )); + state.notify_registered_clients(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove( + *uid, + ))); } // Make sure to remove the player from the logged in list. (See LoginProvider) diff --git a/server/src/lib.rs b/server/src/lib.rs index ce5d0b267e..138ac9c50e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -47,8 +47,8 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ - server::WorldMapMsg, ClientType, DisconnectReason, ServerCharacterScreenMsg, - ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg, + ClientType, DisconnectReason, ServerCharacterScreen, ServerGeneral, ServerInfo, ServerInit, + ServerMsg, WorldMapMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -525,13 +525,13 @@ impl Server { .messages() .for_each(|query_result| match query_result.result { CharacterLoaderResponseType::CharacterList(result) => match result { - Ok(character_list_data) => self.notify_character_screen_client( + Ok(character_list_data) => self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterListUpdate(character_list_data), + ServerCharacterScreen::CharacterListUpdate(character_list_data), ), - Err(error) => self.notify_character_screen_client( + Err(error) => self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterActionError(error.to_string()), + ServerCharacterScreen::CharacterActionError(error.to_string()), ), }, CharacterLoaderResponseType::CharacterData(result) => { @@ -544,9 +544,9 @@ impl Server { // We failed to load data for the character from the DB. Notify the // client to push the state back to character selection, with the error // to display - self.notify_character_screen_client( + self.notify_client( query_result.entity, - ServerCharacterScreenMsg::CharacterDataLoadError(error.to_string()), + ServerCharacterScreen::CharacterDataLoadError(error.to_string()), ); // Clean up the entity data on the server @@ -815,7 +815,7 @@ impl Server { ?client.participant, "to many players, wont allow participant to connect" ); - client.register_stream.send(ServerInitMsg::TooManyPlayers)?; + client.register_stream.send(ServerInit::TooManyPlayers)?; continue; } @@ -839,7 +839,7 @@ impl Server { .get_mut(entity) .unwrap() .register_stream - .send(ServerInitMsg::GameSync { + .send(ServerInit::GameSync { // Send client their entity entity_package: TrackedComps::fetch(&self.state.ecs()) .create_entity_package(entity, None, None, None), @@ -858,32 +858,14 @@ impl Server { pub fn notify_client(&self, entity: EcsEntity, msg: S) where - S: Into, + S: Into, { if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { client.send_msg(msg.into()) } } - pub fn notify_in_game_client(&self, entity: EcsEntity, msg: S) - where - S: Into, - { - if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.send_in_game(msg.into()) - } - } - - pub fn notify_character_screen_client(&self, entity: EcsEntity, msg: S) - where - S: Into, - { - if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.send_character_screen(msg.into()) - } - } - - pub fn notify_registered_clients(&mut self, msg: ServerGeneralMsg) { + pub fn notify_registered_clients(&mut self, msg: ServerGeneral) { self.state.notify_registered_clients(msg); } @@ -963,7 +945,7 @@ impl Server { impl Drop for Server { fn drop(&mut self) { self.state - .notify_registered_clients(ServerGeneralMsg::Disconnect(DisconnectReason::Shutdown)); + .notify_registered_clients(ServerGeneral::Disconnect(DisconnectReason::Shutdown)); } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 2f31784f91..807b553ef9 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -6,8 +6,8 @@ use common::{ comp, effect::Effect, msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreenMsg, ServerGeneralMsg, - ServerInGameMsg, + CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreen, ServerGeneral, + ServerInGame, ServerMsg, }, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -62,7 +62,8 @@ pub trait StateExt { fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); /// Iterates over registered clients and send each `ServerMsg` fn send_chat(&self, msg: comp::UnresolvedChatMsg); - fn notify_registered_clients(&self, msg: ServerGeneralMsg); + fn notify_registered_clients(&self, msg: ServerGeneral); + fn notify_in_game_clients(&self, msg: ServerInGame); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( &mut self, @@ -220,7 +221,7 @@ impl StateExt for State { // Tell the client its request was successful. if let Some(client) = self.ecs().write_storage::().get_mut(entity) { client.in_game = Some(ClientIngame::Character); - client.send_character_screen(ServerCharacterScreenMsg::CharacterSuccess) + client.send_msg(ServerCharacterScreen::CharacterSuccess) } } @@ -229,7 +230,7 @@ impl StateExt for State { if let Some(player_uid) = self.read_component_copied::(entity) { // Notify clients of a player list update - self.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate( + self.notify_registered_clients(ServerGeneral::PlayerListUpdate( PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo { name: String::from(&stats.name), level: stats.level.level(), @@ -276,7 +277,7 @@ impl StateExt for State { | comp::ChatType::Kill(_, _) | comp::ChatType::Meta | comp::ChatType::World(_) => { - self.notify_registered_clients(ServerGeneralMsg::ChatMsg(resolved_msg)) + self.notify_registered_clients(ServerGeneral::ChatMsg(resolved_msg)) }, comp::ChatType::Tell(u, t) => { for (client, uid) in ( @@ -286,7 +287,7 @@ impl StateExt for State { .join() { if uid == u || uid == t { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -298,7 +299,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -310,7 +311,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -322,7 +323,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } } @@ -336,7 +337,7 @@ impl StateExt for State { .join() { if s == &faction.0 { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -348,7 +349,7 @@ impl StateExt for State { .join() { if g == group { - client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone())); + client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone())); } } }, @@ -356,7 +357,8 @@ impl StateExt for State { } /// Sends the message to all connected clients - fn notify_registered_clients(&self, msg: ServerGeneralMsg) { + fn notify_registered_clients(&self, msg: ServerGeneral) { + let msg: ServerMsg = msg.into(); for client in (&mut self.ecs().write_storage::()) .join() .filter(|c| c.registered) @@ -365,6 +367,17 @@ impl StateExt for State { } } + /// Sends the message to all clients playing in game + fn notify_in_game_clients(&self, msg: ServerInGame) { + let msg: ServerMsg = msg.into(); + for client in (&mut self.ecs().write_storage::()) + .join() + .filter(|c| c.in_game.is_some()) + { + client.send_msg(msg.clone()); + } + } + fn delete_entity_recorded( &mut self, entity: EcsEntity, @@ -388,7 +401,7 @@ impl StateExt for State { .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); }, ); } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 46a9e27d43..9f163db12e 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::{ServerGeneralMsg, ServerInGameMsg}, + msg::{ServerGeneral, ServerInGame}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -128,14 +128,13 @@ impl<'a> System<'a> for Sys { (uid, pos, velocities.get(entity), orientations.get(entity)) }) }) { - let create_msg = ServerGeneralMsg::CreateEntity( - tracked_comps.create_entity_package( + let create_msg = + ServerGeneral::CreateEntity(tracked_comps.create_entity_package( entity, Some(*pos), vel.copied(), ori.copied(), - ), - ); + )); for (client, regions, client_entity, _) in &mut subscribers { if maybe_key .as_ref() @@ -158,7 +157,7 @@ impl<'a> System<'a> for Sys { .map(|key| !regions.contains(key)) .unwrap_or(true) { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } } @@ -175,14 +174,14 @@ impl<'a> System<'a> for Sys { .take_deleted_in_region(key) .unwrap_or_default(), ); - let entity_sync_msg = ServerGeneralMsg::EntitySync(entity_sync_package); - let comp_sync_msg = ServerGeneralMsg::CompSync(comp_sync_package); + let entity_sync_msg = ServerGeneral::EntitySync(entity_sync_package); + let comp_sync_msg = ServerGeneral::CompSync(comp_sync_package); subscribers.iter_mut().for_each(move |(client, _, _, _)| { client.send_msg(entity_sync_msg.clone()); client.send_msg(comp_sync_msg.clone()); }); - let mut send_msg = |msg: ServerGeneralMsg, + let mut send_msg = |msg: ServerGeneral, entity: EcsEntity, pos: Pos, force_update: Option<&ForceUpdate>, @@ -288,7 +287,7 @@ impl<'a> System<'a> for Sys { } send_msg( - ServerGeneralMsg::CompSync(comp_sync_package), + ServerGeneral::CompSync(comp_sync_package), entity, pos, force_update, @@ -312,7 +311,7 @@ impl<'a> System<'a> for Sys { }) { for uid in &deleted { - client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid))); } } } @@ -321,7 +320,7 @@ impl<'a> System<'a> for Sys { // Sync inventories for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() { - client.send_in_game(ServerInGameMsg::InventoryUpdate( + client.send_msg(ServerInGame::InventoryUpdate( inventory.clone(), update.event(), )); @@ -342,7 +341,7 @@ impl<'a> System<'a> for Sys { .cloned() .collect::>(); if !outcomes.is_empty() { - client.send_in_game(ServerInGameMsg::Outcomes(outcomes)); + client.send_msg(ServerInGame::Outcomes(outcomes)); } } outcomes.clear(); @@ -354,7 +353,7 @@ impl<'a> System<'a> for Sys { // Sync resources // TODO: doesn't really belong in this system (rename system or create another // system?) - let tof_msg = ServerGeneralMsg::TimeOfDay(*time_of_day); + let tof_msg = ServerGeneral::TimeOfDay(*time_of_day); for client in (&mut clients).join() { client.send_msg(tof_msg.clone()); } diff --git a/server/src/sys/invite_timeout.rs b/server/src/sys/invite_timeout.rs index 2afd405c11..9cf3f71455 100644 --- a/server/src/sys/invite_timeout.rs +++ b/server/src/sys/invite_timeout.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::group::{Invite, PendingInvites}, - msg::{InviteAnswer, ServerInGameMsg}, + msg::{InviteAnswer, ServerInGame}, span, sync::Uid, }; @@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys { if let (Some(client), Some(target)) = (clients.get_mut(*inviter), uids.get(invitee).copied()) { - client.send_in_game(ServerInGameMsg::InviteComplete { + client.send_msg(ServerInGame::InviteComplete { target, answer: InviteAnswer::TimedOut, }) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 1dc5b98a44..70a6afcddb 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,10 +15,10 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreenMsg, - ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientRegisterMsg, DisconnectReason, - PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreenMsg, - ServerGeneralMsg, ServerInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreen, + ClientGeneral, ClientInGame, ClientIngame, ClientRegister, DisconnectReason, PingMsg, + PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreen, ServerGeneral, + ServerInGame, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -45,10 +45,10 @@ impl Sys { player_metrics: &ReadExpect<'_, PlayerMetrics>, uids: &ReadStorage<'_, Uid>, chat_modes: &ReadStorage<'_, ChatMode>, - msg: ClientGeneralMsg, + msg: ClientGeneral, ) -> Result<(), crate::error::Error> { match msg { - ClientGeneralMsg::ChatMsg(message) => { + ClientGeneral::ChatMsg(message) => { if client.registered { match validate_chat_msg(&message) { Ok(()) => { @@ -68,10 +68,10 @@ impl Sys { } } }, - ClientGeneralMsg::Disconnect => { - client.send_msg(ServerGeneralMsg::Disconnect(DisconnectReason::Requested)); + ClientGeneral::Disconnect => { + client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested)); }, - ClientGeneralMsg::Terminate => { + ClientGeneral::Terminate => { debug!(?entity, "Client send message to termitate session"); player_metrics .clients_disconnected @@ -100,24 +100,24 @@ impl Sys { players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, - msg: ClientInGameMsg, + msg: ClientInGame, ) -> Result<(), crate::error::Error> { if client.in_game.is_none() { debug!(?entity, "client is not in_game, ignoring msg"); trace!(?msg, "ignored msg content"); - if matches!(msg, ClientInGameMsg::TerrainChunkRequest{ .. }) { + if matches!(msg, ClientInGame::TerrainChunkRequest{ .. }) { network_metrics.chunks_request_dropped.inc(); } return Ok(()); } match msg { // Go back to registered state (char selection screen) - ClientInGameMsg::ExitInGame => { + ClientInGame::ExitInGame => { client.in_game = None; server_emitter.emit(ServerEvent::ExitIngame { entity }); - client.send_in_game(ServerInGameMsg::ExitInGameSuccess); + client.send_msg(ServerInGame::ExitInGameSuccess); }, - ClientInGameMsg::SetViewDistance(view_distance) => { + ClientInGame::SetViewDistance(view_distance) => { players.get_mut(entity).map(|player| { player.view_distance = Some( settings @@ -133,19 +133,19 @@ impl Sys { .map(|max| view_distance > max) .unwrap_or(false) { - client.send_in_game(ServerInGameMsg::SetViewDistance( + client.send_msg(ServerInGame::SetViewDistance( settings.max_view_distance.unwrap_or(0), )); } }, - ClientInGameMsg::ControllerInputs(inputs) => { + ClientInGame::ControllerInputs(inputs) => { if let Some(ClientIngame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.inputs.update_with_new(inputs); } } }, - ClientInGameMsg::ControlEvent(event) => { + ClientInGame::ControlEvent(event) => { if let Some(ClientIngame::Character) = client.in_game { // Skip respawn if client entity is alive if let ControlEvent::Respawn = event { @@ -159,14 +159,14 @@ impl Sys { } } }, - ClientInGameMsg::ControlAction(event) => { + ClientInGame::ControlAction(event) => { if let Some(ClientIngame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.actions.push(event); } } }, - ClientInGameMsg::PlayerPhysics { pos, vel, ori } => { + ClientInGame::PlayerPhysics { pos, vel, ori } => { if let Some(ClientIngame::Character) = client.in_game { if force_updates.get(entity).is_none() && stats.get(entity).map_or(true, |s| !s.is_dead) @@ -177,17 +177,17 @@ impl Sys { } } }, - ClientInGameMsg::BreakBlock(pos) => { + ClientInGame::BreakBlock(pos) => { if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { block_changes.set(pos, block.into_vacant()); } }, - ClientInGameMsg::PlaceBlock(pos, block) => { + ClientInGame::PlaceBlock(pos, block) => { if can_build.get(entity).is_some() { block_changes.try_set(pos, block); } }, - ClientInGameMsg::TerrainChunkRequest { key } => { + ClientInGame::TerrainChunkRequest { key } => { let in_vd = if let (Some(view_distance), Some(pos)) = ( players.get(entity).and_then(|p| p.view_distance), positions.get(entity), @@ -203,7 +203,7 @@ impl Sys { match terrain.get_key(key) { Some(chunk) => { network_metrics.chunks_served_from_memory.inc(); - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }) @@ -217,17 +217,17 @@ impl Sys { network_metrics.chunks_request_dropped.inc(); } }, - ClientInGameMsg::UnlockSkill(skill) => { + ClientInGame::UnlockSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill(skill)); }, - ClientInGameMsg::RefundSkill(skill) => { + ClientInGame::RefundSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.refund_skill(skill)); }, - ClientInGameMsg::UnlockSkillGroup(skill_group_type) => { + ClientInGame::UnlockSkillGroup(skill_group_type) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); @@ -247,18 +247,18 @@ impl Sys { players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, - msg: ClientCharacterScreenMsg, + msg: ClientCharacterScreen, ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientCharacterScreenMsg::Spectate => { + ClientCharacterScreen::Spectate => { if client.registered { client.in_game = Some(ClientIngame::Spectator) } else { debug!("dropped Spectate msg from unregistered client"); } }, - ClientCharacterScreenMsg::Character(character_id) => { + ClientCharacterScreen::Character(character_id) => { if client.registered && client.in_game.is_none() { // Only send login message if it wasn't already // sent previously @@ -301,11 +301,9 @@ impl Sys { } } } else { - client.send_character_screen( - ServerCharacterScreenMsg::CharacterDataLoadError(String::from( - "Failed to fetch player entity", - )), - ) + client.send_msg(ServerCharacterScreen::CharacterDataLoadError( + String::from("Failed to fetch player entity"), + )) } } else { let registered = client.registered; @@ -313,15 +311,15 @@ impl Sys { debug!(?registered, ?in_game, "dropped Character msg from client"); } }, - ClientCharacterScreenMsg::RequestCharacterList => { + ClientCharacterScreen::RequestCharacterList => { if let Some(player) = players.get(entity) { character_loader.load_character_list(entity, player.uuid().to_string()) } }, - ClientCharacterScreenMsg::CreateCharacter { alias, tool, body } => { + ClientCharacterScreen::CreateCharacter { alias, tool, body } => { if let Err(error) = alias_validator.validate(&alias) { debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.send_character_screen(ServerCharacterScreenMsg::CharacterActionError( + client.send_msg(ServerCharacterScreen::CharacterActionError( error.to_string(), )); } else if let Some(player) = players.get(entity) { @@ -335,7 +333,7 @@ impl Sys { ); } }, - ClientCharacterScreenMsg::DeleteCharacter(character_id) => { + ClientCharacterScreen::DeleteCharacter(character_id) => { if let Some(player) = players.get(entity) { character_loader.delete_character( entity, @@ -351,7 +349,7 @@ impl Sys { #[allow(clippy::too_many_arguments)] fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> { match msg { - PingMsg::Ping => client.send_ping(PingMsg::Pong), + PingMsg::Ping => client.send_msg(PingMsg::Pong), PingMsg::Pong => {}, } Ok(()) @@ -368,7 +366,7 @@ impl Sys { admins: &mut WriteStorage<'_, Admin>, players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, - msg: ClientRegisterMsg, + msg: ClientRegister, ) -> Result<(), crate::error::Error> { let (username, uuid) = match login_provider.try_login( &msg.token_or_username, @@ -379,7 +377,7 @@ impl Sys { Err(err) => { client .register_stream - .send(ServerRegisterAnswerMsg::Err(err))?; + .send(ServerRegisterAnswer::Err(err))?; return Ok(()); }, Ok((username, uuid)) => (username, uuid), @@ -391,9 +389,9 @@ impl Sys { if !player.is_valid() { // Invalid player - client.register_stream.send(ServerRegisterAnswerMsg::Err( - RegisterError::InvalidCharacter, - ))?; + client + .register_stream + .send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; return Ok(()); } @@ -410,12 +408,10 @@ impl Sys { // Tell the client its request was successful. client.registered = true; - client - .register_stream - .send(ServerRegisterAnswerMsg::Ok(()))?; + client.register_stream.send(ServerRegisterAnswer::Ok(()))?; // Send initial player list - client.send_msg(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init( + client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( player_list.clone(), ))); @@ -458,7 +454,7 @@ impl Sys { alias_validator: &ReadExpect<'_, AliasValidator>, ) -> Result<(), crate::error::Error> { loop { - let q1 = Client::internal_recv(&client.network_error, &mut client.singleton_stream); + let q1 = Client::internal_recv(&client.network_error, &mut client.general_stream); let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream); let q3 = Client::internal_recv(&client.network_error, &mut client.character_screen_stream); @@ -693,7 +689,7 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::ClientDisconnect(entity)); } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { // Try pinging the client if the timeout is nearing. - client.send_ping(PingMsg::Ping); + client.send_msg(PingMsg::Ping); } } @@ -702,7 +698,7 @@ impl<'a> System<'a> for Sys { for entity in new_players { if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { let msg = - ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { player_alias: player.alias.clone(), is_online: true, is_admin: admins.get(entity).is_some(), diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index ba5f3906df..2eac60f2a6 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -5,7 +5,7 @@ use super::{ use crate::client::{self, Client, RegionSubscription}; use common::{ comp::{Ori, Player, Pos, Vel}, - msg::ServerGeneralMsg, + msg::ServerGeneral, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, span, sync::Uid, @@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys { .map(|key| subscription.regions.contains(key)) .unwrap_or(false) { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } }, @@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys { } // Tell client to delete entities in the region for (&uid, _) in (&uids, region.entities()).join() { - client.send_msg(ServerGeneralMsg::DeleteEntity(uid)); + client.send_msg(ServerGeneral::DeleteEntity(uid)); } } // Send deleted entities since they won't be processed for this client in entity @@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys { .iter() .flat_map(|v| v.iter()) { - client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid))); + client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid))); } } @@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys { { // Send message to create entity and tracked components and physics // components - client.send_msg(ServerGeneralMsg::CreateEntity( + client.send_msg(ServerGeneral::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), @@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) { .join() { // Send message to create entity and tracked components and physics components - client.send_msg(ServerGeneralMsg::CreateEntity( + client.send_msg(ServerGeneral::CreateEntity( tracked_comps.create_entity_package( entity, Some(*pos), diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 8ca477e0fd..c29330addf 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,7 +4,7 @@ use common::{ comp::{self, bird_medium, Alignment, Player, Pos}, event::{EventBus, ServerEvent}, generation::get_npc_name, - msg::ServerInGameMsg, + msg::ServerInGame, npc::NPC_NAMES, span, state::TerrainChanges, @@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys { Ok((chunk, supplement)) => (chunk, supplement), Err(Some(entity)) => { if let Some(client) = clients.get_mut(entity) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Err(()), }); @@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys { .magnitude_squared(); if adjusted_dist_sqr <= view_distance.pow(2) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }); diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index d0d1df5376..876b3b94eb 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos}, - msg::ServerInGameMsg, + msg::ServerInGame, span, state::TerrainChanges, terrain::TerrainGrid, @@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys { .map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd)) .unwrap_or(false) { - client.send_in_game(ServerInGameMsg::TerrainChunkUpdate { + client.send_msg(ServerInGame::TerrainChunkUpdate { key: *chunk_key, chunk: Ok(Box::new(match terrain.get_key(*chunk_key) { Some(chunk) => chunk.clone(), @@ -51,10 +51,10 @@ impl<'a> System<'a> for Sys { // TODO: Don't send all changed blocks to all clients // Sync changed blocks - let msg = ServerInGameMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); + let msg = ServerInGame::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); for (player, client) in (&players, &mut clients).join() { if player.view_distance.is_some() { - client.send_in_game(msg.clone()); + client.send_msg(msg.clone()); } } diff --git a/server/src/sys/waypoint.rs b/server/src/sys/waypoint.rs index 63642e135a..bf8034baa0 100644 --- a/server/src/sys/waypoint.rs +++ b/server/src/sys/waypoint.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos, Waypoint, WaypointArea}, - msg::{Notification, ServerGeneralMsg}, + msg::{Notification, ServerGeneral}, span, state::Time, }; @@ -42,9 +42,8 @@ impl<'a> System<'a> for Sys { if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time)) { if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) { - client.send_msg(ServerGeneralMsg::Notification( - Notification::WaypointSaved, - )); + client + .send_msg(ServerGeneral::Notification(Notification::WaypointSaved)); } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 5e5a322229..7aa02fb46c 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -38,7 +38,7 @@ use crate::{ use common::{ comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small}, generation::{ChunkSupplement, EntityInfo}, - msg::server::WorldMapMsg, + msg::WorldMapMsg, terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, WriteVol}, }; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 58ec0de7ea..5f67f0712b 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -33,7 +33,7 @@ use crate::{ }; use common::{ assets, - msg::server::WorldMapMsg, + msg::WorldMapMsg, store::Id, terrain::{ map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg, From 00c66b5b9c385cad3a2fa808473e44d1e277b0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Wed, 7 Oct 2020 13:59:14 +0200 Subject: [PATCH 7/9] remove a Mutex and AtomicBool --- server/src/client.rs | 57 +++++++++------------------- server/src/connection_handler.rs | 4 +- server/src/events/player.rs | 15 ++++---- server/src/sys/message.rs | 64 ++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 76 deletions(-) diff --git a/server/src/client.rs b/server/src/client.rs index f3db6ee99e..9b33e6f5f8 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,17 +1,10 @@ use crate::error::Error; -use common::msg::{ - ClientCharacterScreen, ClientGeneral, ClientInGame, ClientIngame, ClientType, PingMsg, - ServerMsg, -}; +use common::msg::{ClientIngame, ClientType, ServerMsg}; use hashbrown::HashSet; use network::{Participant, Stream}; use serde::{de::DeserializeOwned, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Mutex, -}; use tracing::debug; use vek::*; @@ -19,13 +12,13 @@ pub struct Client { pub registered: bool, pub client_type: ClientType, pub in_game: Option, - pub participant: Mutex>, + pub participant: Option, pub general_stream: Stream, pub ping_stream: Stream, pub register_stream: Stream, pub character_screen_stream: Stream, pub in_game_stream: Stream, - pub network_error: AtomicBool, + pub network_error: bool, pub last_ping: f64, pub login_msg_sent: bool, } @@ -35,11 +28,11 @@ impl Component for Client { } impl Client { - fn internal_send(b: &AtomicBool, s: &mut Stream, msg: M) { - if !b.load(Ordering::Relaxed) { + fn internal_send(err: &mut bool, s: &mut Stream, msg: M) { + if !*err { if let Err(e) = s.send(msg) { debug!(?e, "got a network error with client"); - b.store(true, Ordering::Relaxed); + *err = true; } } } @@ -64,33 +57,35 @@ impl Client { ServerMsg::Info(_) => panic!(ERR), ServerMsg::Init(_) => panic!(ERR), ServerMsg::RegisterAnswer(msg) => { - Self::internal_send(&self.network_error, &mut self.register_stream, &msg) - }, - ServerMsg::CharacterScreen(msg) => { - Self::internal_send(&self.network_error, &mut self.character_screen_stream, &msg) + Self::internal_send(&mut self.network_error, &mut self.register_stream, &msg) }, + ServerMsg::CharacterScreen(msg) => Self::internal_send( + &mut self.network_error, + &mut self.character_screen_stream, + &msg, + ), ServerMsg::InGame(msg) => { - Self::internal_send(&self.network_error, &mut self.in_game_stream, &msg) + Self::internal_send(&mut self.network_error, &mut self.in_game_stream, &msg) }, ServerMsg::General(msg) => { - Self::internal_send(&self.network_error, &mut self.general_stream, &msg) + Self::internal_send(&mut self.network_error, &mut self.general_stream, &msg) }, ServerMsg::Ping(msg) => { - Self::internal_send(&self.network_error, &mut self.ping_stream, &msg) + Self::internal_send(&mut self.network_error, &mut self.ping_stream, &msg) }, }; } pub async fn internal_recv( - b: &AtomicBool, + err: &mut bool, s: &mut Stream, ) -> Result { - if !b.load(Ordering::Relaxed) { + if !*err { match s.recv().await { Ok(r) => Ok(r), Err(e) => { debug!(?e, "got a network error with client while recv"); - b.store(true, Ordering::Relaxed); + *err = true; Err(Error::StreamErr(e)) }, } @@ -98,22 +93,6 @@ impl Client { Err(Error::StreamErr(network::StreamError::StreamClosed)) } } - - pub async fn recv_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.general_stream).await - } - - pub async fn recv_in_game_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.in_game_stream).await - } - - pub async fn recv_character_screen_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.character_screen_stream).await - } - - pub async fn recv_ping_msg(&mut self) -> Result { - Self::internal_recv(&self.network_error, &mut self.ping_stream).await - } } // Distance from fuzzy_chunk before snapping to current chunk diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index a2c7145f58..6617a78214 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -133,13 +133,13 @@ impl ConnectionHandler { registered: false, client_type, in_game: None, - participant: std::sync::Mutex::new(Some(participant)), + participant: Some(participant), general_stream, ping_stream, register_stream, in_game_stream, character_screen_stream, - network_error: std::sync::atomic::AtomicBool::new(false), + network_error: false, last_ping: server_data.time, login_msg_sent: false, }; diff --git a/server/src/events/player.rs b/server/src/events/player.rs index db6a164a14..50380a8349 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -89,14 +89,13 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event { span!(_guard, "handle_client_disconnect"); - if let Some(client) = server.state().read_storage::().get(entity) { - let participant = match client.participant.try_lock() { - Ok(mut p) => p.take().unwrap(), - Err(e) => { - error!(?e, ?entity, "couldn't lock participant for removal"); - return Event::ClientDisconnected { entity }; - }, - }; + if let Some(client) = server + .state() + .ecs() + .write_storage::() + .get_mut(entity) + { + let participant = client.participant.take().unwrap(); let pid = participant.remote_pid(); std::thread::spawn(move || { let span = tracing::span!(tracing::Level::DEBUG, "client_disconnect", ?pid, ?entity); diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 70a6afcddb..07badca7cc 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -453,23 +453,30 @@ impl Sys { editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, ) -> Result<(), crate::error::Error> { + let (mut b1, mut b2, mut b3, mut b4, mut b5) = ( + client.network_error, + client.network_error, + client.network_error, + client.network_error, + client.network_error, + ); loop { - let q1 = Client::internal_recv(&client.network_error, &mut client.general_stream); - let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream); - let q3 = - Client::internal_recv(&client.network_error, &mut client.character_screen_stream); - let q4 = Client::internal_recv(&client.network_error, &mut client.ping_stream); - let q5 = Client::internal_recv(&client.network_error, &mut client.register_stream); + let q1 = Client::internal_recv(&mut b1, &mut client.general_stream); + let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream); + let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream); + let q4 = Client::internal_recv(&mut b4, &mut client.ping_stream); + let q5 = Client::internal_recv(&mut b5, &mut client.register_stream); let (m1, m2, m3, m4, m5) = select!( - msg = q1.fuse() => (Some(msg?), None, None, None, None), - msg = q2.fuse() => (None, Some(msg?), None, None, None), - msg = q3.fuse() => (None, None, Some(msg?), None, None), - msg = q4.fuse() => (None, None, None, Some(msg?), None), - msg = q5.fuse() => (None, None, None, None,Some(msg?)), + msg = q1.fuse() => (Some(msg), None, None, None, None), + msg = q2.fuse() => (None, Some(msg), None, None, None), + msg = q3.fuse() => (None, None, Some(msg), None, None), + msg = q4.fuse() => (None, None, None, Some(msg), None), + msg = q5.fuse() => (None, None, None, None,Some(msg)), ); *cnt += 1; if let Some(msg) = m1 { + client.network_error |= b1; Self::handle_client_msg( server_emitter, new_chat_msgs, @@ -478,10 +485,11 @@ impl Sys { player_metrics, uids, chat_modes, - msg, + msg?, )?; } if let Some(msg) = m2 { + client.network_error |= b2; Self::handle_client_in_game_msg( server_emitter, entity, @@ -498,10 +506,11 @@ impl Sys { players, controllers, settings, - msg, + msg?, )?; } if let Some(msg) = m3 { + client.network_error |= b3; Self::handle_client_character_screen_msg( server_emitter, new_chat_msgs, @@ -512,13 +521,15 @@ impl Sys { players, editable_settings, alias_validator, - msg, + msg?, )?; } if let Some(msg) = m4 { - Self::handle_ping_msg(client, msg)?; + client.network_error |= b4; + Self::handle_ping_msg(client, msg?)?; } if let Some(msg) = m5 { + client.network_error |= b5; Self::handle_register_msg( player_list, new_players, @@ -529,7 +540,7 @@ impl Sys { admins, players, editable_settings, - msg, + msg?, )?; } } @@ -666,8 +677,16 @@ impl<'a> System<'a> for Sys { ) }); - // Update client ping. - if cnt > 0 { + // Postbox error + if network_err.is_err() { + debug!(?entity, "postbox error with client, disconnecting"); + player_metrics + .clients_disconnected + .with_label_values(&["network_error"]) + .inc(); + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); + } else if cnt > 0 { + // Update client ping. client.last_ping = time.0 } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 // Timeout @@ -678,15 +697,6 @@ impl<'a> System<'a> for Sys { .with_label_values(&["timeout"]) .inc(); server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - } else if network_err.is_err() - // Postbox error - { - debug!(?entity, "postbox error with client, disconnecting"); - player_metrics - .clients_disconnected - .with_label_values(&["network_error"]) - .inc(); - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { // Try pinging the client if the timeout is nearing. client.send_msg(PingMsg::Ping); From 55b59fbe070520eae66d64bff393717c2a8f8354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 12 Oct 2020 00:14:04 +0200 Subject: [PATCH 8/9] various small fixes according to the MR --- Cargo.lock | 13 ++-- client/src/lib.rs | 11 +-- server/Cargo.toml | 1 + server/src/connection_handler.rs | 55 ++++++------- server/src/sys/message.rs | 104 ++++++++++++------------- voxygen/src/menu/char_selection/mod.rs | 2 +- voxygen/src/session.rs | 2 +- 7 files changed, 90 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 177e2ef47e..17330dfba1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1393,9 +1393,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74" dependencies = [ "futures-core", "futures-sink", @@ -1403,9 +1403,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" [[package]] name = "futures-cpupool" @@ -1449,9 +1449,9 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" +checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd" [[package]] name = "futures-task" @@ -4689,6 +4689,7 @@ dependencies = [ "diesel", "diesel_migrations", "dotenv", + "futures-channel", "futures-executor", "futures-timer", "futures-util", diff --git a/client/src/lib.rs b/client/src/lib.rs index 8efadb48a4..2f22d94242 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -466,7 +466,7 @@ impl Client { let msg: ClientMsg = msg.into(); #[cfg(debug_assertions)] { - //There assertions veriy that the state is correct when a msg is send! + // These assertions verify that the state is correct when a message is sent! match &msg { ClientMsg::Type(_) | ClientMsg::Register(_) => assert!( !self.registered, @@ -1514,19 +1514,14 @@ impl Client { Ok(frontend_events) } - /// Get the player's entity. pub fn entity(&self) -> EcsEntity { self.entity } - /// Get the player's Uid. pub fn uid(&self) -> Option { self.state.read_component_copied(self.entity) } - pub fn get_client_type(&self) -> ClientType { ClientType::Game } + pub fn in_game(&self) -> Option { self.in_game } - pub fn get_in_game(&self) -> Option { self.in_game } + pub fn registered(&self) -> bool { self.registered } - pub fn get_registered(&self) -> bool { self.registered } - - /// Get the current tick number. pub fn get_tick(&self) -> u64 { self.tick } pub fn get_ping_ms(&self) -> f64 { self.last_ping_delta * 1000.0 } diff --git a/server/Cargo.toml b/server/Cargo.toml index 7eb4747105..952973055c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -22,6 +22,7 @@ uvth = "3.1.1" futures-util = "0.3" futures-executor = "0.3" futures-timer = "2.0" +futures-channel = "0.3" itertools = "0.9" lazy_static = "1.4.0" scan_fmt = "0.2.4" diff --git a/server/src/connection_handler.rs b/server/src/connection_handler.rs index 6617a78214..2f79f1795d 100644 --- a/server/src/connection_handler.rs +++ b/server/src/connection_handler.rs @@ -1,17 +1,11 @@ use crate::{Client, ClientType, ServerInfo}; use crossbeam::{bounded, unbounded, Receiver, Sender}; +use futures_channel::oneshot; use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; use network::{Network, Participant, Promises}; -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread, - time::Duration, -}; +use std::{sync::Arc, thread, time::Duration}; use tracing::{debug, error, trace, warn}; pub(crate) struct ServerInfoPacket { @@ -19,28 +13,25 @@ pub(crate) struct ServerInfoPacket { pub time: f64, } -pub(crate) type ConnectionDataPacket = Client; - pub(crate) struct ConnectionHandler { _network: Arc, thread_handle: Option>, - pub client_receiver: Receiver, + pub client_receiver: Receiver, pub info_requester_receiver: Receiver>, - running: Arc, + stop_sender: Option>, } /// Instead of waiting the main loop we are handling connections, especially /// their slow network .await part on a different thread. We need to communicate -/// to the Server main thread sometimes tough to get the current server_info and -/// time +/// to the Server main thread sometimes though to get the current server_info +/// and time impl ConnectionHandler { pub fn new(network: Network) -> Self { let network = Arc::new(network); let network_clone = Arc::clone(&network); - let running = Arc::new(AtomicBool::new(true)); - let running_clone = Arc::clone(&running); + let (stop_sender, stop_receiver) = oneshot::channel(); - let (client_sender, client_receiver) = unbounded::(); + let (client_sender, client_receiver) = unbounded::(); let (info_requester_sender, info_requester_receiver) = bounded::>(1); @@ -49,7 +40,7 @@ impl ConnectionHandler { network_clone, client_sender, info_requester_sender, - running_clone, + stop_receiver, )); })); @@ -58,23 +49,23 @@ impl ConnectionHandler { thread_handle, client_receiver, info_requester_receiver, - running, + stop_sender: Some(stop_sender), } } async fn work( network: Arc, - client_sender: Sender, + client_sender: Sender, info_requester_sender: Sender>, - running: Arc, + stop_receiver: oneshot::Receiver<()>, ) { - while running.load(Ordering::Relaxed) { - const TIMEOUT: Duration = Duration::from_secs(5); + let mut stop_receiver = stop_receiver.fuse(); + loop { let participant = match select!( - _ = Delay::new(TIMEOUT).fuse() => None, + _ = stop_receiver => None, p = network.connected().fuse() => Some(p), ) { - None => continue, //check condition + None => break, Some(Ok(p)) => p, Some(Err(e)) => { error!( @@ -88,16 +79,20 @@ impl ConnectionHandler { let client_sender = client_sender.clone(); let info_requester_sender = info_requester_sender.clone(); - match Self::init_participant(participant, client_sender, info_requester_sender).await { - Ok(_) => (), - Err(e) => warn!(?e, "drop new participant, because an error occurred"), + match select!( + _ = stop_receiver => None, + e = Self::init_participant(participant, client_sender, info_requester_sender).fuse() => Some(e), + ) { + None => break, + Some(Ok(())) => (), + Some(Err(e)) => warn!(?e, "drop new participant, because an error occurred"), } } } async fn init_participant( participant: Participant, - client_sender: Sender, + client_sender: Sender, info_requester_sender: Sender>, ) -> Result<(), Box> { debug!("New Participant connected to the server"); @@ -151,7 +146,7 @@ impl ConnectionHandler { impl Drop for ConnectionHandler { fn drop(&mut self) { - self.running.store(false, Ordering::Relaxed); + let _ = self.stop_sender.take().unwrap().send(()); trace!("blocking till ConnectionHandler is closed"); self.thread_handle .take() diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 07badca7cc..16631c6234 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -251,65 +251,60 @@ impl Sys { ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientCharacterScreen::Spectate => { - if client.registered { - client.in_game = Some(ClientIngame::Spectator) - } else { - debug!("dropped Spectate msg from unregistered client"); - } + ClientCharacterScreen::Spectate if client.registered => { + client.in_game = Some(ClientIngame::Spectator) }, - ClientCharacterScreen::Character(character_id) => { - if client.registered && client.in_game.is_none() { - // Only send login message if it wasn't already - // sent previously - if let Some(player) = players.get(entity) { - // Send a request to load the character's component data from the - // DB. Once loaded, persisted components such as stats and inventory - // will be inserted for the entity - character_loader.load_character_data( - entity, - player.uuid().to_string(), - character_id, + ClientCharacterScreen::Spectate => { + debug!("dropped Spectate msg from unregistered client") + }, + ClientCharacterScreen::Character(character_id) + if client.registered && client.in_game.is_none() => + { + if let Some(player) = players.get(entity) { + // Send a request to load the character's component data from the + // DB. Once loaded, persisted components such as stats and inventory + // will be inserted for the entity + character_loader.load_character_data( + entity, + player.uuid().to_string(), + character_id, + ); + + // Start inserting non-persisted/default components for the entity + // while we load the DB data + server_emitter.emit(ServerEvent::InitCharacterData { + entity, + character_id, + }); + + // Give the player a welcome message + if !editable_settings.server_description.is_empty() { + client.send_msg( + ChatType::CommandInfo + .server_msg(String::from(&*editable_settings.server_description)), ); + } - // Start inserting non-persisted/default components for the entity - // while we load the DB data - server_emitter.emit(ServerEvent::InitCharacterData { - entity, - character_id, - }); + if !client.login_msg_sent { + if let Some(player_uid) = uids.get(entity) { + new_chat_msgs.push((None, UnresolvedChatMsg { + chat_type: ChatType::Online(*player_uid), + message: "".to_string(), + })); - // Give the player a welcome message - if !editable_settings.server_description.is_empty() { - client.send_msg( - ChatType::CommandInfo.server_msg(String::from( - &*editable_settings.server_description, - )), - ); + client.login_msg_sent = true; } - - // Only send login message if it wasn't already - // sent previously - if !client.login_msg_sent { - if let Some(player_uid) = uids.get(entity) { - new_chat_msgs.push((None, UnresolvedChatMsg { - chat_type: ChatType::Online(*player_uid), - message: "".to_string(), - })); - - client.login_msg_sent = true; - } - } - } else { - client.send_msg(ServerCharacterScreen::CharacterDataLoadError( - String::from("Failed to fetch player entity"), - )) } } else { - let registered = client.registered; - let in_game = client.in_game; - debug!(?registered, ?in_game, "dropped Character msg from client"); + client.send_msg(ServerCharacterScreen::CharacterDataLoadError(String::from( + "Failed to fetch player entity", + ))) } + } + ClientCharacterScreen::Character(_) => { + let registered = client.registered; + let in_game = client.in_game; + debug!(?registered, ?in_game, "dropped Character msg from client"); }, ClientCharacterScreen::RequestCharacterList => { if let Some(player) = players.get(entity) { @@ -461,6 +456,11 @@ impl Sys { client.network_error, ); loop { + /* + waiting for 1 of the 5 streams to return a massage asynchronous. + If so, handle that msg type. This code will be refactored soon + */ + let q1 = Client::internal_recv(&mut b1, &mut client.general_stream); let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream); let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream); @@ -677,7 +677,7 @@ impl<'a> System<'a> for Sys { ) }); - // Postbox error + // Network error if network_err.is_err() { debug!(?entity, "postbox error with client, disconnecting"); player_metrics diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 434c9c565f..fe80710b07 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -63,7 +63,7 @@ impl PlayState for CharSelectionState { span!(_guard, "tick", "::tick"); let (client_in_game, client_registered) = { let client = self.client.borrow(); - (client.get_in_game(), client.get_registered()) + (client.in_game(), client.registered()) }; if client_in_game.is_none() && client_registered { // Handle window events diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index f4538232a8..6162696608 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -213,7 +213,7 @@ impl PlayState for SessionState { // TODO: can this be a method on the session or are there borrowcheck issues? let (client_in_game, client_registered) = { let client = self.client.borrow(); - (client.get_in_game(), client.get_registered()) + (client.in_game(), client.registered()) }; if client_in_game.is_some() { // Update MyEntity From 2a7378b4ae0fd7688dc6ca403e1d272dfce988a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 12 Oct 2020 10:18:28 +0200 Subject: [PATCH 9/9] pack together InGame, CharacterScreen and General variant in a single enum, as requested by zesterer. His reason to reqeust that is, that there might not be a perfect disctinction in the future. Now we need to send ServerGeneral over streams and do additional checking at various places to verify that not the wrong variant is send. --- client/src/lib.rs | 178 ++++++++++++----------- common/src/msg/client.rs | 79 ++++++---- common/src/msg/mod.rs | 9 +- common/src/msg/server.rs | 102 ++++++++----- server/src/client.rs | 44 ++++-- server/src/cmd.rs | 6 +- server/src/events/entity_manipulation.rs | 4 +- server/src/events/group_manip.rs | 18 +-- server/src/events/inventory_manip.rs | 4 +- server/src/events/player.rs | 4 +- server/src/lib.rs | 9 +- server/src/state_ext.rs | 15 +- server/src/sys/entity_sync.rs | 6 +- server/src/sys/invite_timeout.rs | 4 +- server/src/sys/message.rs | 80 +++++----- server/src/sys/terrain.rs | 6 +- server/src/sys/terrain_sync.rs | 6 +- 17 files changed, 331 insertions(+), 243 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 2f22d94242..01c24a81c5 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,10 +25,9 @@ use common::{ }, event::{EventBus, LocalEvent}, msg::{ - validate_chat_msg, ChatMsgValidationError, ClientCharacterScreen, ClientGeneral, - ClientInGame, ClientIngame, ClientMsg, ClientRegister, ClientType, DisconnectReason, - InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, - ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, + validate_chat_msg, ChatMsgValidationError, ClientGeneral, ClientInGame, ClientMsg, + ClientRegister, ClientType, DisconnectReason, InviteAnswer, Notification, PingMsg, + PlayerInfo, PlayerListUpdate, RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, outcome::Outcome, @@ -71,7 +70,7 @@ pub enum Event { pub struct Client { registered: bool, - in_game: Option, + in_game: Option, thread_pool: ThreadPool, pub server_info: ServerInfo, /// Just the "base" layer for LOD; currently includes colors and nothing @@ -466,39 +465,46 @@ impl Client { let msg: ClientMsg = msg.into(); #[cfg(debug_assertions)] { - // These assertions verify that the state is correct when a message is sent! - match &msg { - ClientMsg::Type(_) | ClientMsg::Register(_) => assert!( - !self.registered, - "must not send msg when already registered" - ), - ClientMsg::CharacterScreen(_) => { - assert!( - self.registered, - "must not send character_screen msg when not registered" - ); - assert!( - self.in_game.is_none(), - "must not send character_screen msg when not in character screen" - ); - }, - ClientMsg::InGame(_) => assert!( - self.in_game.is_some(), - "must not send in_game msg when not in game" - ), - ClientMsg::General(_) => assert!( - self.registered, - "must not send general msg when not registered" - ), - ClientMsg::Ping(_) => (), - } + const C_TYPE: ClientType = ClientType::Game; + let verified = msg.verify(C_TYPE, self.registered, self.in_game); + assert!( + verified, + format!( + "c_type: {:?}, registered: {}, in_game: {:?}, msg: {:?}", + C_TYPE, self.registered, self.in_game, msg + ) + ); } match msg { ClientMsg::Type(msg) => self.register_stream.send(msg), ClientMsg::Register(msg) => self.register_stream.send(msg), - ClientMsg::CharacterScreen(msg) => self.character_screen_stream.send(msg), - ClientMsg::InGame(msg) => self.in_game_stream.send(msg), - ClientMsg::General(msg) => self.general_stream.send(msg), + ClientMsg::General(msg) => { + let stream = match msg { + ClientGeneral::RequestCharacterList + | ClientGeneral::CreateCharacter { .. } + | ClientGeneral::DeleteCharacter(_) + | ClientGeneral::Character(_) + | ClientGeneral::Spectate => &mut self.character_screen_stream, + //Only in game + ClientGeneral::ControllerInputs(_) + | ClientGeneral::ControlEvent(_) + | ClientGeneral::ControlAction(_) + | ClientGeneral::SetViewDistance(_) + | ClientGeneral::BreakBlock(_) + | ClientGeneral::PlaceBlock(_, _) + | ClientGeneral::ExitInGame + | ClientGeneral::PlayerPhysics { .. } + | ClientGeneral::TerrainChunkRequest { .. } + | ClientGeneral::UnlockSkill(_) + | ClientGeneral::RefundSkill(_) + | ClientGeneral::UnlockSkillGroup(_) => &mut self.in_game_stream, + //Always possible + ClientGeneral::ChatMsg(_) + | ClientGeneral::Disconnect + | ClientGeneral::Terminate => &mut self.general_stream, + }; + stream.send(msg) + }, ClientMsg::Ping(msg) => self.ping_stream.send(msg), } } @@ -518,10 +524,10 @@ impl Client { /// Request a state transition to `ClientState::Character`. pub fn request_character(&mut self, character_id: CharacterId) { - self.send_msg(ClientCharacterScreen::Character(character_id)); + self.send_msg(ClientGeneral::Character(character_id)); //Assume we are in_game unless server tells us otherwise - self.in_game = Some(ClientIngame::Character); + self.in_game = Some(ClientInGame::Character); self.active_character_id = Some(character_id); } @@ -529,19 +535,19 @@ impl Client { /// Load the current players character list pub fn load_character_list(&mut self) { self.character_list.loading = true; - self.send_msg(ClientCharacterScreen::RequestCharacterList); + self.send_msg(ClientGeneral::RequestCharacterList); } /// New character creation pub fn create_character(&mut self, alias: String, tool: Option, body: comp::Body) { self.character_list.loading = true; - self.send_msg(ClientCharacterScreen::CreateCharacter { alias, tool, body }); + self.send_msg(ClientGeneral::CreateCharacter { alias, tool, body }); } /// Character deletion pub fn delete_character(&mut self, character_id: CharacterId) { self.character_list.loading = true; - self.send_msg(ClientCharacterScreen::DeleteCharacter(character_id)); + self.send_msg(ClientGeneral::DeleteCharacter(character_id)); } /// Send disconnect message to the server @@ -552,34 +558,34 @@ impl Client { /// Request a state transition to `ClientState::Registered` from an ingame /// state. - pub fn request_remove_character(&mut self) { self.send_msg(ClientInGame::ExitInGame); } + pub fn request_remove_character(&mut self) { self.send_msg(ClientGeneral::ExitInGame); } pub fn set_view_distance(&mut self, view_distance: u32) { self.view_distance = Some(view_distance.max(1).min(65)); - self.send_msg(ClientInGame::SetViewDistance(self.view_distance.unwrap())); + self.send_msg(ClientGeneral::SetViewDistance(self.view_distance.unwrap())); } pub fn use_slot(&mut self, slot: comp::slot::Slot) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Use(slot), ))); } pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Swap(a, b), ))); } pub fn drop_slot(&mut self, slot: comp::slot::Slot) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Drop(slot), ))); } pub fn pick_up(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Pickup(uid), ))); } @@ -599,7 +605,7 @@ impl Client { pub fn craft_recipe(&mut self, recipe: &str) -> bool { if self.can_craft_recipe(recipe) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::CraftRecipe(recipe.to_string()), ))); true @@ -618,11 +624,11 @@ impl Client { } pub fn enable_lantern(&mut self) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::EnableLantern)); + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::EnableLantern)); } pub fn disable_lantern(&mut self) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::DisableLantern)); + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); } pub fn max_group_size(&self) -> u32 { self.max_group_size } @@ -640,7 +646,7 @@ impl Client { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } pub fn send_group_invite(&mut self, invitee: Uid) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Invite(invitee), ))) } @@ -648,7 +654,7 @@ impl Client { pub fn accept_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Accept, ))); } @@ -656,25 +662,25 @@ impl Client { pub fn decline_group_invite(&mut self) { // Clear invite self.group_invite.take(); - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Decline, ))); } pub fn leave_group(&mut self) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Leave, ))); } pub fn kick_from_group(&mut self, uid: Uid) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Kick(uid), ))); } pub fn assign_group_leader(&mut self, uid: Uid) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::AssignLeader(uid), ))); } @@ -697,11 +703,11 @@ impl Client { pub fn mount(&mut self, entity: EcsEntity) { if let Some(uid) = self.state.read_component_copied(entity) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::Mount(uid))); + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Mount(uid))); } } - pub fn unmount(&mut self) { self.send_msg(ClientInGame::ControlEvent(ControlEvent::Unmount)); } + pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); } pub fn respawn(&mut self) { if self @@ -711,7 +717,7 @@ impl Client { .get(self.entity) .map_or(false, |s| s.is_dead) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::Respawn)); + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Respawn)); } } @@ -808,7 +814,7 @@ impl Client { { controller.actions.push(control_action); } - self.send_msg(ClientInGame::ControlAction(control_action)); + self.send_msg(ClientGeneral::ControlAction(control_action)); } pub fn view_distance(&self) -> Option { self.view_distance } @@ -852,13 +858,15 @@ impl Client { } pub fn place_block(&mut self, pos: Vec3, block: Block) { - self.send_msg(ClientInGame::PlaceBlock(pos, block)); + self.send_msg(ClientGeneral::PlaceBlock(pos, block)); } - pub fn remove_block(&mut self, pos: Vec3) { self.send_msg(ClientInGame::BreakBlock(pos)); } + pub fn remove_block(&mut self, pos: Vec3) { + self.send_msg(ClientGeneral::BreakBlock(pos)); + } pub fn collect_block(&mut self, pos: Vec3) { - self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip( + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Collect(pos), ))); } @@ -915,7 +923,7 @@ impl Client { "Couldn't access controller component on client entity" ); } - self.send_msg_err(ClientInGame::ControllerInputs(inputs))?; + self.send_msg_err(ClientGeneral::ControllerInputs(inputs))?; } // 2) Build up a list of events for this frame, to be passed to the frontend. @@ -1019,7 +1027,7 @@ impl Client { if self.state.terrain().get_key(*key).is_none() { if !skip_mode && !self.pending_chunks.contains_key(key) { if self.pending_chunks.len() < 4 { - self.send_msg_err(ClientInGame::TerrainChunkRequest { + self.send_msg_err(ClientGeneral::TerrainChunkRequest { key: *key, })?; self.pending_chunks.insert(*key, Instant::now()); @@ -1065,7 +1073,7 @@ impl Client { self.state.read_storage().get(self.entity).cloned(), ) { self.in_game_stream - .send(ClientInGame::PlayerPhysics { pos, vel, ori })?; + .send(ClientGeneral::PlayerPhysics { pos, vel, ori })?; } } @@ -1237,6 +1245,7 @@ impl Client { ServerGeneral::Notification(n) => { frontend_events.push(Event::Notification(n)); }, + _ => unreachable!("Not a general msg"), } Ok(()) } @@ -1244,10 +1253,10 @@ impl Client { fn handle_server_in_game_msg( &mut self, frontend_events: &mut Vec, - msg: ServerInGame, + msg: ServerGeneral, ) -> Result<(), Error> { match msg { - ServerInGame::GroupUpdate(change_notification) => { + ServerGeneral::GroupUpdate(change_notification) => { use comp::group::ChangeNotification::*; // Note: we use a hashmap since this would not work with entities outside // the view distance @@ -1319,15 +1328,15 @@ impl Client { }, } }, - ServerInGame::GroupInvite { inviter, timeout } => { + ServerGeneral::GroupInvite { inviter, timeout } => { self.group_invite = Some((inviter, std::time::Instant::now(), timeout)); }, - ServerInGame::InvitePending(uid) => { + ServerGeneral::InvitePending(uid) => { if !self.pending_invites.insert(uid) { warn!("Received message about pending invite that was already pending"); } }, - ServerInGame::InviteComplete { target, answer } => { + ServerGeneral::InviteComplete { target, answer } => { if !self.pending_invites.remove(&target) { warn!( "Received completed invite message for invite that was not in the list of \ @@ -1345,11 +1354,11 @@ impl Client { frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg))); }, // Cleanup for when the client goes back to the `in_game = None` - ServerInGame::ExitInGameSuccess => { + ServerGeneral::ExitInGameSuccess => { self.in_game = None; self.clean_state(); }, - ServerInGame::InventoryUpdate(mut inventory, event) => { + ServerGeneral::InventoryUpdate(mut inventory, event) => { match event { InventoryUpdateEvent::CollectFailed => {}, _ => { @@ -1363,25 +1372,25 @@ impl Client { frontend_events.push(Event::InventoryUpdated(event)); }, - ServerInGame::TerrainChunkUpdate { key, chunk } => { + ServerGeneral::TerrainChunkUpdate { key, chunk } => { if let Ok(chunk) = chunk { self.state.insert_chunk(key, *chunk); } self.pending_chunks.remove(&key); }, - ServerInGame::TerrainBlockUpdates(mut blocks) => { + ServerGeneral::TerrainBlockUpdates(mut blocks) => { blocks.drain().for_each(|(pos, block)| { self.state.set_block(pos, block); }); }, - ServerInGame::SetViewDistance(vd) => { + ServerGeneral::SetViewDistance(vd) => { self.view_distance = Some(vd); frontend_events.push(Event::SetViewDistance(vd)); }, - ServerInGame::Outcomes(outcomes) => { + ServerGeneral::Outcomes(outcomes) => { frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) }, - ServerInGame::Knockback(impulse) => { + ServerGeneral::Knockback(impulse) => { self.state .ecs() .read_resource::>() @@ -1390,35 +1399,34 @@ impl Client { impulse, }); }, + _ => unreachable!("Not a in_game message"), } Ok(()) } - fn handle_server_character_screen_msg( - &mut self, - msg: ServerCharacterScreen, - ) -> Result<(), Error> { + fn handle_server_character_screen_msg(&mut self, msg: ServerGeneral) -> Result<(), Error> { match msg { - ServerCharacterScreen::CharacterListUpdate(character_list) => { + ServerGeneral::CharacterListUpdate(character_list) => { self.character_list.characters = character_list; self.character_list.loading = false; }, - ServerCharacterScreen::CharacterActionError(error) => { + ServerGeneral::CharacterActionError(error) => { warn!("CharacterActionError: {:?}.", error); self.character_list.error = Some(error); }, - ServerCharacterScreen::CharacterDataLoadError(error) => { + ServerGeneral::CharacterDataLoadError(error) => { trace!("Handling join error by server"); self.in_game = None; self.clean_state(); self.character_list.error = Some(error); }, - ServerCharacterScreen::CharacterSuccess => { + ServerGeneral::CharacterSuccess => { debug!("client is now in ingame state on server"); if let Some(vd) = self.view_distance { self.set_view_distance(vd); } }, + _ => unreachable!("Not a character_screen msg"), } Ok(()) } @@ -1518,7 +1526,7 @@ impl Client { pub fn uid(&self) -> Option { self.state.read_component_copied(self.entity) } - pub fn in_game(&self) -> Option { self.in_game } + pub fn in_game(&self) -> Option { self.in_game } pub fn registered(&self) -> bool { self.registered } diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 2339bcab12..e8b8be3b02 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -12,16 +12,13 @@ use vek::*; /// streams though). It's used to verify the correctness of the state in /// debug_assertions #[derive(Debug, Clone)] +#[allow(clippy::clippy::large_enum_variant)] pub enum ClientMsg { ///Send on the first connection ONCE to identify client intention for /// server Type(ClientType), ///Send ONCE to register/auth to the server Register(ClientRegister), - ///Msg only to send while in character screen, e.g. `CreateCharacter` - CharacterScreen(ClientCharacterScreen), - ///Msg only to send while playing in game, e.g. `PlayerPositionUpdates` - InGame(ClientInGame), ///Msg that can be send ALWAYS as soon as we are registered, e.g. `Chat` General(ClientGeneral), Ping(PingMsg), @@ -47,9 +44,10 @@ pub struct ClientRegister { pub token_or_username: String, } -//messages send by clients only valid when in character screen -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientCharacterScreen { +/// Messages sent from the client to the server +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ClientGeneral { + //Only in Character Screen RequestCharacterList, CreateCharacter { alias: String, @@ -59,11 +57,7 @@ pub enum ClientCharacterScreen { DeleteCharacter(CharacterId), Character(CharacterId), Spectate, -} - -//messages send by clients only valid when in game (with a character) -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ClientInGame { + //Only in game ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), ControlAction(comp::ControlAction), @@ -82,16 +76,59 @@ pub enum ClientInGame { UnlockSkill(Skill), RefundSkill(Skill), UnlockSkillGroup(SkillGroupType), -} - -/// Messages sent from the client to the server -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClientGeneral { + //Always possible ChatMsg(String), Disconnect, Terminate, } +impl ClientMsg { + pub fn verify( + &self, + c_type: ClientType, + registered: bool, + in_game: Option, + ) -> bool { + match self { + ClientMsg::Type(t) => c_type == *t, + ClientMsg::Register(_) => !registered && in_game.is_none(), + ClientMsg::General(g) => { + registered + && match g { + ClientGeneral::RequestCharacterList + | ClientGeneral::CreateCharacter { .. } + | ClientGeneral::DeleteCharacter(_) => { + c_type != ClientType::ChatOnly && in_game.is_none() + }, + ClientGeneral::Character(_) | ClientGeneral::Spectate => { + c_type == ClientType::Game && in_game.is_none() + }, + //Only in game + ClientGeneral::ControllerInputs(_) + | ClientGeneral::ControlEvent(_) + | ClientGeneral::ControlAction(_) + | ClientGeneral::SetViewDistance(_) + | ClientGeneral::BreakBlock(_) + | ClientGeneral::PlaceBlock(_, _) + | ClientGeneral::ExitInGame + | ClientGeneral::PlayerPhysics { .. } + | ClientGeneral::TerrainChunkRequest { .. } + | ClientGeneral::UnlockSkill(_) + | ClientGeneral::RefundSkill(_) + | ClientGeneral::UnlockSkillGroup(_) => { + c_type == ClientType::Game && in_game.is_some() + }, + //Always possible + ClientGeneral::ChatMsg(_) + | ClientGeneral::Disconnect + | ClientGeneral::Terminate => true, + } + }, + ClientMsg::Ping(_) => true, + } + } +} + /* end of 2nd level Enums */ @@ -104,14 +141,6 @@ impl Into for ClientRegister { fn into(self) -> ClientMsg { ClientMsg::Register(self) } } -impl Into for ClientCharacterScreen { - fn into(self) -> ClientMsg { ClientMsg::CharacterScreen(self) } -} - -impl Into for ClientInGame { - fn into(self) -> ClientMsg { ClientMsg::InGame(self) } -} - impl Into for ClientGeneral { fn into(self) -> ClientMsg { ClientMsg::General(self) } } diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index b4ea9fe6fe..a7e4802a89 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -5,21 +5,18 @@ pub mod world_packet; // Reexports pub use self::{ - client::{ - ClientCharacterScreen, ClientGeneral, ClientInGame, ClientMsg, ClientRegister, ClientType, - }, + client::{ClientGeneral, ClientMsg, ClientRegister, ClientType}, ecs_packet::EcsCompPacket, server::{ CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, - RegisterError, ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit, - ServerMsg, ServerRegisterAnswer, + RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerMsg, ServerRegisterAnswer, }, world_packet::WorldMapMsg, }; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ClientIngame { +pub enum ClientInGame { Spectator, Character, } diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 82b861ef6e..b54ca91a4b 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,4 +1,4 @@ -use super::{EcsCompPacket, PingMsg}; +use super::{ClientType, EcsCompPacket, PingMsg}; use crate::{ character::CharacterItem, comp, @@ -24,12 +24,6 @@ pub enum ServerMsg { Init(ServerInit), /// Result to `ClientMsg::Register`. send ONCE RegisterAnswer(ServerRegisterAnswer), - /// Msg only to send when client is on the character screen, e.g. - /// `CharacterListUpdate` - CharacterScreen(ServerCharacterScreen), - /// Msg only to send when client is playing in game, e.g. - /// `TerrainChunkUpdate` - InGame(ServerInGame), ///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat` General(ServerGeneral), Ping(PingMsg), @@ -65,9 +59,10 @@ pub enum ServerInit { pub type ServerRegisterAnswer = Result<(), RegisterError>; -//Messages only allowed while client in character screen +/// Messages sent from the server to the client #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerCharacterScreen { +pub enum ServerGeneral { + //Character Screen related /// An error occurred while loading character data CharacterDataLoadError(String), /// A list of characters belonging to the a authenticated player was sent @@ -75,23 +70,21 @@ pub enum ServerCharacterScreen { /// An error occurred while creating or deleting a character CharacterActionError(String), CharacterSuccess, -} - -//Messages only allowed while client is in game (with a character) -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerInGame { + //Ingame related GroupUpdate(comp::group::ChangeNotification), - // Indicate to the client that they are invited to join a group + /// Indicate to the client that they are invited to join a group GroupInvite { inviter: sync::Uid, timeout: std::time::Duration, }, - // Indicate to the client that their sent invite was not invalid and is currently pending + /// Indicate to the client that their sent invite was not invalid and is + /// currently pending InvitePending(sync::Uid), - // Note: this could potentially include all the failure cases such as inviting yourself in - // which case the `InvitePending` message could be removed and the client could consider their - // invite pending until they receive this message - // Indicate to the client the result of their invite + /// Note: this could potentially include all the failure cases such as + /// inviting yourself in which case the `InvitePending` message could be + /// removed and the client could consider their invite pending until + /// they receive this message Indicate to the client the result of their + /// invite InviteComplete { target: sync::Uid, answer: InviteAnswer, @@ -108,11 +101,7 @@ pub enum ServerInGame { SetViewDistance(u32), Outcomes(Vec), Knockback(Vec3), -} - -/// Messages sent from the server to the client -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ServerGeneral { + // Always possible PlayerListUpdate(PlayerListUpdate), /// A message to go into the client chat box. The client is responsible for /// formatting the message and turning it into a speech bubble. @@ -190,6 +179,61 @@ pub enum RegisterError { //TODO: InvalidAlias, } +impl ServerMsg { + pub fn verify( + &self, + c_type: ClientType, + registered: bool, + in_game: Option, + ) -> bool { + match self { + ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => { + !registered && in_game.is_none() + }, + ServerMsg::General(g) => { + registered + && match g { + //Character Screen related + ServerGeneral::CharacterDataLoadError(_) + | ServerGeneral::CharacterListUpdate(_) + | ServerGeneral::CharacterActionError(_) => { + c_type != ClientType::ChatOnly && in_game.is_none() + }, + ServerGeneral::CharacterSuccess => { + c_type == ClientType::Game && in_game.is_none() + }, + //Ingame related + ServerGeneral::GroupUpdate(_) + | ServerGeneral::GroupInvite { .. } + | ServerGeneral::InvitePending(_) + | ServerGeneral::InviteComplete { .. } + | ServerGeneral::ExitInGameSuccess + | ServerGeneral::InventoryUpdate(_, _) + | ServerGeneral::TerrainChunkUpdate { .. } + | ServerGeneral::TerrainBlockUpdates(_) + | ServerGeneral::SetViewDistance(_) + | ServerGeneral::Outcomes(_) + | ServerGeneral::Knockback(_) => { + c_type == ClientType::Game && in_game.is_some() + }, + // Always possible + ServerGeneral::PlayerListUpdate(_) + | ServerGeneral::ChatMsg(_) + | ServerGeneral::SetPlayerEntity(_) + | ServerGeneral::TimeOfDay(_) + | ServerGeneral::EntitySync(_) + | ServerGeneral::CompSync(_) + | ServerGeneral::CreateEntity(_) + | ServerGeneral::DeleteEntity(_) + | ServerGeneral::Disconnect(_) + | ServerGeneral::Notification(_) => true, + } + }, + ServerMsg::Ping(_) => true, + } + } +} + impl From for RegisterError { fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) } } @@ -210,14 +254,6 @@ impl Into for ServerRegisterAnswer { fn into(self) -> ServerMsg { ServerMsg::RegisterAnswer(self) } } -impl Into for ServerCharacterScreen { - fn into(self) -> ServerMsg { ServerMsg::CharacterScreen(self) } -} - -impl Into for ServerInGame { - fn into(self) -> ServerMsg { ServerMsg::InGame(self) } -} - impl Into for ServerGeneral { fn into(self) -> ServerMsg { ServerMsg::General(self) } } diff --git a/server/src/client.rs b/server/src/client.rs index 9b33e6f5f8..e2bfcf6d6f 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,5 +1,5 @@ use crate::error::Error; -use common::msg::{ClientIngame, ClientType, ServerMsg}; +use common::msg::{ClientInGame, ClientType, ServerGeneral, ServerMsg}; use hashbrown::HashSet; use network::{Participant, Stream}; use serde::{de::DeserializeOwned, Serialize}; @@ -11,7 +11,7 @@ use vek::*; pub struct Client { pub registered: bool, pub client_type: ClientType, - pub in_game: Option, + pub in_game: Option, pub participant: Option, pub general_stream: Stream, pub ping_stream: Stream, @@ -59,16 +59,38 @@ impl Client { ServerMsg::RegisterAnswer(msg) => { Self::internal_send(&mut self.network_error, &mut self.register_stream, &msg) }, - ServerMsg::CharacterScreen(msg) => Self::internal_send( - &mut self.network_error, - &mut self.character_screen_stream, - &msg, - ), - ServerMsg::InGame(msg) => { - Self::internal_send(&mut self.network_error, &mut self.in_game_stream, &msg) - }, ServerMsg::General(msg) => { - Self::internal_send(&mut self.network_error, &mut self.general_stream, &msg) + let stream = match &msg { + //Character Screen related + ServerGeneral::CharacterDataLoadError(_) + | ServerGeneral::CharacterListUpdate(_) + | ServerGeneral::CharacterActionError(_) + | ServerGeneral::CharacterSuccess => &mut self.character_screen_stream, + //Ingame related + ServerGeneral::GroupUpdate(_) + | ServerGeneral::GroupInvite { .. } + | ServerGeneral::InvitePending(_) + | ServerGeneral::InviteComplete { .. } + | ServerGeneral::ExitInGameSuccess + | ServerGeneral::InventoryUpdate(_, _) + | ServerGeneral::TerrainChunkUpdate { .. } + | ServerGeneral::TerrainBlockUpdates(_) + | ServerGeneral::SetViewDistance(_) + | ServerGeneral::Outcomes(_) + | ServerGeneral::Knockback(_) => &mut self.in_game_stream, + // Always possible + ServerGeneral::PlayerListUpdate(_) + | ServerGeneral::ChatMsg(_) + | ServerGeneral::SetPlayerEntity(_) + | ServerGeneral::TimeOfDay(_) + | ServerGeneral::EntitySync(_) + | ServerGeneral::CompSync(_) + | ServerGeneral::CreateEntity(_) + | ServerGeneral::DeleteEntity(_) + | ServerGeneral::Disconnect(_) + | ServerGeneral::Notification(_) => &mut self.general_stream, + }; + Self::internal_send(&mut self.network_error, stream, &msg) }, ServerMsg::Ping(msg) => { Self::internal_send(&mut self.network_error, &mut self.ping_stream, &msg) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 4d0b61d807..1d07f1e837 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,7 +12,7 @@ use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, - msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral, ServerInGame}, + msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral}, npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, @@ -669,7 +669,9 @@ fn handle_spawn( .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| { + c.send_msg(ServerGeneral::GroupUpdate(g)) + }); }, ); } else if let Some(group) = match alignment { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d480302e82..21e3221744 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -12,7 +12,7 @@ use common::{ Player, Pos, Stats, }, lottery::Lottery, - msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, + msg::{PlayerListUpdate, ServerGeneral}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, @@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3) } let mut clients = state.ecs().write_storage::(); if let Some(client) = clients.get_mut(entity) { - client.send_msg(ServerInGame::Knockback(impulse)); + client.send_msg(ServerGeneral::Knockback(impulse)); } } diff --git a/server/src/events/group_manip.rs b/server/src/events/group_manip.rs index bbaf5c0b32..78c0df462b 100644 --- a/server/src/events/group_manip.rs +++ b/server/src/events/group_manip.rs @@ -5,7 +5,7 @@ use common::{ group::{self, Group, GroupManager, Invite, PendingInvites}, ChatType, GroupManip, }, - msg::{InviteAnswer, ServerInGame}, + msg::{InviteAnswer, ServerGeneral}, sync, sync::WorldSyncExt, }; @@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani (clients.get_mut(invitee), uids.get(entity).copied()) { if send_invite() { - client.send_msg(ServerInGame::GroupInvite { + client.send_msg(ServerGeneral::GroupInvite { inviter, timeout: PRESENTED_INVITE_TIMEOUT_DUR, }); @@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani // Notify inviter that the invite is pending if invite_sent { if let Some(client) = clients.get_mut(entity) { - client.send_msg(ServerInGame::InvitePending(uid)); + client.send_msg(ServerGeneral::InvitePending(uid)); } } }, @@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_msg(ServerInGame::InviteComplete { + client.send_msg(ServerGeneral::InviteComplete { target, answer: InviteAnswer::Accepted, }) @@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g))); }, ); } @@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani if let (Some(client), Some(target)) = (clients.get_mut(inviter), uids.get(entity).copied()) { - client.send_msg(ServerInGame::InviteComplete { + client.send_msg(ServerGeneral::InviteComplete { target, answer: InviteAnswer::Declined, }) @@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g))); }, ); }, @@ -336,7 +336,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g))); }, ); @@ -410,7 +410,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g))); }, ); // Tell them they are the leader diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 2f39db3811..c8f2bbd512 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -5,7 +5,7 @@ use common::{ slot::{self, Slot}, Pos, MAX_PICKUP_RANGE_SQR, }, - msg::ServerInGame, + msg::ServerGeneral, recipe::default_recipe_book, sync::{Uid, WorldSyncExt}, vol::ReadVol, @@ -281,7 +281,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .map(|g| (g, c)) }) .map(|(g, c)| { - c.send_msg(ServerInGame::GroupUpdate(g)) + c.send_msg(ServerGeneral::GroupUpdate(g)) }); }, ); diff --git a/server/src/events/player.rs b/server/src/events/player.rs index 50380a8349..e466a5b116 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -5,7 +5,7 @@ use crate::{ use common::{ comp, comp::{group, Player}, - msg::{PlayerListUpdate, ServerGeneral, ServerInGame}, + msg::{PlayerListUpdate, ServerGeneral}, span, sync::{Uid, UidAllocator}, }; @@ -34,7 +34,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { // Tell client its request was successful client.in_game = None; - client.send_msg(ServerInGame::ExitInGameSuccess); + client.send_msg(ServerGeneral::ExitInGameSuccess); let entity_builder = state.ecs_mut().create_entity().with(client).with(player); diff --git a/server/src/lib.rs b/server/src/lib.rs index 138ac9c50e..d2a6111f05 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -47,8 +47,7 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ - ClientType, DisconnectReason, ServerCharacterScreen, ServerGeneral, ServerInfo, ServerInit, - ServerMsg, WorldMapMsg, + ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg, }, outcome::Outcome, recipe::default_recipe_book, @@ -527,11 +526,11 @@ impl Server { CharacterLoaderResponseType::CharacterList(result) => match result { Ok(character_list_data) => self.notify_client( query_result.entity, - ServerCharacterScreen::CharacterListUpdate(character_list_data), + ServerGeneral::CharacterListUpdate(character_list_data), ), Err(error) => self.notify_client( query_result.entity, - ServerCharacterScreen::CharacterActionError(error.to_string()), + ServerGeneral::CharacterActionError(error.to_string()), ), }, CharacterLoaderResponseType::CharacterData(result) => { @@ -546,7 +545,7 @@ impl Server { // to display self.notify_client( query_result.entity, - ServerCharacterScreen::CharacterDataLoadError(error.to_string()), + ServerGeneral::CharacterDataLoadError(error.to_string()), ); // Clean up the entity data on the server diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 807b553ef9..2b2c173a38 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -5,10 +5,7 @@ use common::{ character::CharacterId, comp, effect::Effect, - msg::{ - CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreen, ServerGeneral, - ServerInGame, ServerMsg, - }, + msg::{CharacterInfo, ClientInGame, PlayerListUpdate, ServerGeneral, ServerMsg}, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, util::Dir, @@ -63,7 +60,7 @@ pub trait StateExt { /// Iterates over registered clients and send each `ServerMsg` fn send_chat(&self, msg: comp::UnresolvedChatMsg); fn notify_registered_clients(&self, msg: ServerGeneral); - fn notify_in_game_clients(&self, msg: ServerInGame); + fn notify_in_game_clients(&self, msg: ServerGeneral); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( &mut self, @@ -220,8 +217,8 @@ impl StateExt for State { // Tell the client its request was successful. if let Some(client) = self.ecs().write_storage::().get_mut(entity) { - client.in_game = Some(ClientIngame::Character); - client.send_msg(ServerCharacterScreen::CharacterSuccess) + client.in_game = Some(ClientInGame::Character); + client.send_msg(ServerGeneral::CharacterSuccess) } } @@ -368,7 +365,7 @@ impl StateExt for State { } /// Sends the message to all clients playing in game - fn notify_in_game_clients(&self, msg: ServerInGame) { + fn notify_in_game_clients(&self, msg: ServerGeneral) { let msg: ServerMsg = msg.into(); for client in (&mut self.ecs().write_storage::()) .join() @@ -401,7 +398,7 @@ impl StateExt for State { .try_map(|e| uids.get(e).copied()) .map(|g| (g, c)) }) - .map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g))); + .map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g))); }, ); } diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 9f163db12e..0442a04e8e 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -8,7 +8,7 @@ use crate::{ }; use common::{ comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, - msg::{ServerGeneral, ServerInGame}, + msg::ServerGeneral, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, span, @@ -320,7 +320,7 @@ impl<'a> System<'a> for Sys { // Sync inventories for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() { - client.send_msg(ServerInGame::InventoryUpdate( + client.send_msg(ServerGeneral::InventoryUpdate( inventory.clone(), update.event(), )); @@ -341,7 +341,7 @@ impl<'a> System<'a> for Sys { .cloned() .collect::>(); if !outcomes.is_empty() { - client.send_msg(ServerInGame::Outcomes(outcomes)); + client.send_msg(ServerGeneral::Outcomes(outcomes)); } } outcomes.clear(); diff --git a/server/src/sys/invite_timeout.rs b/server/src/sys/invite_timeout.rs index 9cf3f71455..42f6603fe8 100644 --- a/server/src/sys/invite_timeout.rs +++ b/server/src/sys/invite_timeout.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::group::{Invite, PendingInvites}, - msg::{InviteAnswer, ServerInGame}, + msg::{InviteAnswer, ServerGeneral}, span, sync::Uid, }; @@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys { if let (Some(client), Some(target)) = (clients.get_mut(*inviter), uids.get(invitee).copied()) { - client.send_msg(ServerInGame::InviteComplete { + client.send_msg(ServerGeneral::InviteComplete { target, answer: InviteAnswer::TimedOut, }) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 16631c6234..9a9dfc92d3 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -15,10 +15,9 @@ use common::{ }, event::{EventBus, ServerEvent}, msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreen, - ClientGeneral, ClientInGame, ClientIngame, ClientRegister, DisconnectReason, PingMsg, - PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreen, ServerGeneral, - ServerInGame, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneral, ClientInGame, + ClientRegister, DisconnectReason, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, + ServerGeneral, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, }, span, state::{BlockChange, Time}, @@ -79,6 +78,7 @@ impl Sys { .inc(); server_emitter.emit(ServerEvent::ClientDisconnect(entity)); }, + _ => unreachable!("not a client_general msg"), } Ok(()) } @@ -100,24 +100,24 @@ impl Sys { players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, - msg: ClientInGame, + msg: ClientGeneral, ) -> Result<(), crate::error::Error> { if client.in_game.is_none() { debug!(?entity, "client is not in_game, ignoring msg"); trace!(?msg, "ignored msg content"); - if matches!(msg, ClientInGame::TerrainChunkRequest{ .. }) { + if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) { network_metrics.chunks_request_dropped.inc(); } return Ok(()); } match msg { // Go back to registered state (char selection screen) - ClientInGame::ExitInGame => { + ClientGeneral::ExitInGame => { client.in_game = None; server_emitter.emit(ServerEvent::ExitIngame { entity }); - client.send_msg(ServerInGame::ExitInGameSuccess); + client.send_msg(ServerGeneral::ExitInGameSuccess); }, - ClientInGame::SetViewDistance(view_distance) => { + ClientGeneral::SetViewDistance(view_distance) => { players.get_mut(entity).map(|player| { player.view_distance = Some( settings @@ -133,20 +133,20 @@ impl Sys { .map(|max| view_distance > max) .unwrap_or(false) { - client.send_msg(ServerInGame::SetViewDistance( + client.send_msg(ServerGeneral::SetViewDistance( settings.max_view_distance.unwrap_or(0), )); } }, - ClientInGame::ControllerInputs(inputs) => { - if let Some(ClientIngame::Character) = client.in_game { + ClientGeneral::ControllerInputs(inputs) => { + if let Some(ClientInGame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.inputs.update_with_new(inputs); } } }, - ClientInGame::ControlEvent(event) => { - if let Some(ClientIngame::Character) = client.in_game { + ClientGeneral::ControlEvent(event) => { + if let Some(ClientInGame::Character) = client.in_game { // Skip respawn if client entity is alive if let ControlEvent::Respawn = event { if stats.get(entity).map_or(true, |s| !s.is_dead) { @@ -159,15 +159,15 @@ impl Sys { } } }, - ClientInGame::ControlAction(event) => { - if let Some(ClientIngame::Character) = client.in_game { + ClientGeneral::ControlAction(event) => { + if let Some(ClientInGame::Character) = client.in_game { if let Some(controller) = controllers.get_mut(entity) { controller.actions.push(event); } } }, - ClientInGame::PlayerPhysics { pos, vel, ori } => { - if let Some(ClientIngame::Character) = client.in_game { + ClientGeneral::PlayerPhysics { pos, vel, ori } => { + if let Some(ClientInGame::Character) = client.in_game { if force_updates.get(entity).is_none() && stats.get(entity).map_or(true, |s| !s.is_dead) { @@ -177,17 +177,17 @@ impl Sys { } } }, - ClientInGame::BreakBlock(pos) => { + ClientGeneral::BreakBlock(pos) => { if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { block_changes.set(pos, block.into_vacant()); } }, - ClientInGame::PlaceBlock(pos, block) => { + ClientGeneral::PlaceBlock(pos, block) => { if can_build.get(entity).is_some() { block_changes.try_set(pos, block); } }, - ClientInGame::TerrainChunkRequest { key } => { + ClientGeneral::TerrainChunkRequest { key } => { let in_vd = if let (Some(view_distance), Some(pos)) = ( players.get(entity).and_then(|p| p.view_distance), positions.get(entity), @@ -203,7 +203,7 @@ impl Sys { match terrain.get_key(key) { Some(chunk) => { network_metrics.chunks_served_from_memory.inc(); - client.send_msg(ServerInGame::TerrainChunkUpdate { + client.send_msg(ServerGeneral::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }) @@ -217,21 +217,22 @@ impl Sys { network_metrics.chunks_request_dropped.inc(); } }, - ClientInGame::UnlockSkill(skill) => { + ClientGeneral::UnlockSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill(skill)); }, - ClientInGame::RefundSkill(skill) => { + ClientGeneral::RefundSkill(skill) => { stats .get_mut(entity) .map(|s| s.skill_set.refund_skill(skill)); }, - ClientInGame::UnlockSkillGroup(skill_group_type) => { + ClientGeneral::UnlockSkillGroup(skill_group_type) => { stats .get_mut(entity) .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); }, + _ => unreachable!("not a client_in_game msg"), } Ok(()) } @@ -247,17 +248,15 @@ impl Sys { players: &mut WriteStorage<'_, Player>, editable_settings: &ReadExpect<'_, EditableSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, - msg: ClientCharacterScreen, + msg: ClientGeneral, ) -> Result<(), crate::error::Error> { match msg { // Request spectator state - ClientCharacterScreen::Spectate if client.registered => { - client.in_game = Some(ClientIngame::Spectator) + ClientGeneral::Spectate if client.registered => { + client.in_game = Some(ClientInGame::Spectator) }, - ClientCharacterScreen::Spectate => { - debug!("dropped Spectate msg from unregistered client") - }, - ClientCharacterScreen::Character(character_id) + ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"), + ClientGeneral::Character(character_id) if client.registered && client.in_game.is_none() => { if let Some(player) = players.get(entity) { @@ -296,27 +295,25 @@ impl Sys { } } } else { - client.send_msg(ServerCharacterScreen::CharacterDataLoadError(String::from( + client.send_msg(ServerGeneral::CharacterDataLoadError(String::from( "Failed to fetch player entity", ))) } } - ClientCharacterScreen::Character(_) => { + ClientGeneral::Character(_) => { let registered = client.registered; let in_game = client.in_game; debug!(?registered, ?in_game, "dropped Character msg from client"); }, - ClientCharacterScreen::RequestCharacterList => { + ClientGeneral::RequestCharacterList => { if let Some(player) = players.get(entity) { character_loader.load_character_list(entity, player.uuid().to_string()) } }, - ClientCharacterScreen::CreateCharacter { alias, tool, body } => { + ClientGeneral::CreateCharacter { alias, tool, body } => { if let Err(error) = alias_validator.validate(&alias) { debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.send_msg(ServerCharacterScreen::CharacterActionError( - error.to_string(), - )); + client.send_msg(ServerGeneral::CharacterActionError(error.to_string())); } else if let Some(player) = players.get(entity) { character_creator::create_character( entity, @@ -328,7 +325,7 @@ impl Sys { ); } }, - ClientCharacterScreen::DeleteCharacter(character_id) => { + ClientGeneral::DeleteCharacter(character_id) => { if let Some(player) = players.get(entity) { character_loader.delete_character( entity, @@ -337,6 +334,7 @@ impl Sys { ); } }, + _ => unreachable!("not a client_character_screen msg"), } Ok(()) } @@ -379,7 +377,7 @@ impl Sys { }; const INITIAL_VD: Option = Some(5); //will be changed after login - let player = Player::new(username.clone(), None, INITIAL_VD, uuid); + let player = Player::new(username, None, INITIAL_VD, uuid); let is_admin = editable_settings.admins.contains(&uuid); if !player.is_valid() { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index c29330addf..eafa24b0ff 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,7 +4,7 @@ use common::{ comp::{self, bird_medium, Alignment, Player, Pos}, event::{EventBus, ServerEvent}, generation::get_npc_name, - msg::ServerInGame, + msg::ServerGeneral, npc::NPC_NAMES, span, state::TerrainChanges, @@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys { Ok((chunk, supplement)) => (chunk, supplement), Err(Some(entity)) => { if let Some(client) = clients.get_mut(entity) { - client.send_msg(ServerInGame::TerrainChunkUpdate { + client.send_msg(ServerGeneral::TerrainChunkUpdate { key, chunk: Err(()), }); @@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys { .magnitude_squared(); if adjusted_dist_sqr <= view_distance.pow(2) { - client.send_msg(ServerInGame::TerrainChunkUpdate { + client.send_msg(ServerGeneral::TerrainChunkUpdate { key, chunk: Ok(Box::new(chunk.clone())), }); diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index 876b3b94eb..a2e21f42a9 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -2,7 +2,7 @@ use super::SysTimer; use crate::client::Client; use common::{ comp::{Player, Pos}, - msg::ServerInGame, + msg::ServerGeneral, span, state::TerrainChanges, terrain::TerrainGrid, @@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys { .map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd)) .unwrap_or(false) { - client.send_msg(ServerInGame::TerrainChunkUpdate { + client.send_msg(ServerGeneral::TerrainChunkUpdate { key: *chunk_key, chunk: Ok(Box::new(match terrain.get_key(*chunk_key) { Some(chunk) => chunk.clone(), @@ -51,7 +51,7 @@ impl<'a> System<'a> for Sys { // TODO: Don't send all changed blocks to all clients // Sync changed blocks - let msg = ServerInGame::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); + let msg = ServerGeneral::TerrainBlockUpdates(terrain_changes.modified_blocks.clone()); for (player, client) in (&players, &mut clients).join() { if player.view_distance.is_some() { client.send_msg(msg.clone());