From d0715824ed58a6a1c7c541ca35b04b5a8385c15b Mon Sep 17 00:00:00 2001 From: caelunshun Date: Thu, 18 Jul 2019 11:25:34 -0600 Subject: [PATCH 01/30] Start on refactoring to using Specs --- core/src/world/mod.rs | 5 + server/src/chunkclient.rs | 111 +++++++++++++++++++ server/src/chunkworker.rs | 2 +- server/src/config.rs | 29 +++++ server/src/entity.rs | 28 +++++ server/src/genindex.rs | 193 -------------------------------- server/src/main.rs | 227 +++++++++++--------------------------- 7 files changed, 239 insertions(+), 356 deletions(-) create mode 100644 server/src/chunkclient.rs create mode 100644 server/src/entity.rs delete mode 100644 server/src/genindex.rs diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 6ca2b0524..3df5a49ab 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -102,6 +102,11 @@ impl ChunkMap { Err(()) } } + + /// Sets the chunk at the given location. + pub fn set_chunk_at(&mut self, pos: ChunkPosition, chunk: Chunk) { + self.chunk_map.insert(pos, chunk); + } } fn chunk_relative_pos(block_pos: BlockPosition) -> (usize, usize, usize) { diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs new file mode 100644 index 000000000..9629a72da --- /dev/null +++ b/server/src/chunkclient.rs @@ -0,0 +1,111 @@ +//! Module for interacting with the chunk worker thread +//! from the server threads. +use crate::chunkworker; +use crossbeam::channel::{Receiver, Sender}; +use feather_core::world::{ChunkMap, ChunkPosition}; +use specs::{prelude::Resources, Read, System, World, Write}; + +pub struct ChunkWorkerHandle { + sender: Sender, + receiver: Receiver, +} + +/// System for receiving loaded chunks from the chunk worker thread. +pub struct ChunkSystem; + +impl<'a> System<'a> for ChunkSystem { + type SystemData = (Write<'a, ChunkMap>, Read<'a, ChunkWorkerHandle>); + + fn run(&mut self, data: Self::SystemData) { + let (mut chunk_map, handle) = data; + + while let Ok((pos, result)) = handle.receiver.try_recv() { + match result { + Ok(chunk) => { + chunk_map.set_chunk_at(pos, chunk); + trace!("Loaded chunk at {:?}", pos); + } + Err(err) => { + // TODO generate chunk if it didn't exist + warn!("Failed to load chunk at {:?}: {:?}", pos, err); + } + } + } + } + + fn setup(&mut self, world: &mut World) { + use specs::prelude::SystemData; + + info!("Starting chunk worker thread"); + let handle = chunkworker::start("world"); + world.insert(handle); + + Self::SystemData::setup(world); + } +} + +/// Asynchronously loads the chunk at the given position. +/// At some point in time after this function is called, +/// the chunk will appear in the chunk map. +/// +/// In the event that the requested chunk does not exist +/// in the world save, it will be generated asynchronously. +pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { + // Send request to chunk worker thread + handle + .sender + .send(chunkworker::Request::LoadChunk(pos)) + .unwrap(); +} + +#[cfg(test)] +mod tests { + use super::*; + use feather_core::world::chunk::Chunk; + use feather_core::world::ChunkPosition; + use specs::{RunNow, World, WorldExt}; + + #[test] + fn test_chunk_system() { + let (send1, recv1) = crossbeam::channel::unbounded(); + let (send2, recv2) = crossbeam::channel::unbounded(); + let handle = ChunkWorkerHandle { + sender: send1, + receiver: recv2, + }; + + let chunk_map = ChunkMap::new(); + let pos = ChunkPosition::new(0, 0); + send2.send((pos, Ok(Chunk::new(pos)))); + + let mut system = ChunkSystem; + let mut world = World::new(); + world.insert(chunk_map); + world.insert(handle); + + system.run_now(&world.res); + + // Confirm that chunk was loaded + let chunk = world.read_resource::().chunk_at(pos); + + assert!(chunk.is_some()); + assert!(chunk.unwrap().position() == pos); + } + + #[test] + fn test_load_chunk() { + let (send1, recv1) = crossbeam::channel::unbounded(); + let (send2, recv2) = crossbeam::channel::unbounded(); + let handle = ChunkWorkerHandle { + sender: send1, + receiver: recv2, + }; + + let pos = ChunkPosition::new(0, 0); + + load_chunk(&handle, pos); + + let recv = recv1.try_recv().unwrap(); + assert_eq!(recv, chunkworker::Request::LoadChunk(pos)); + } +} diff --git a/server/src/chunkworker.rs b/server/src/chunkworker.rs index ebf43bb58..ba0833ffe 100644 --- a/server/src/chunkworker.rs +++ b/server/src/chunkworker.rs @@ -16,7 +16,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; pub type Reply = (ChunkPosition, Result); -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Request { LoadChunk(ChunkPosition), ShutDown, diff --git a/server/src/config.rs b/server/src/config.rs index ebe3973a5..a4d53af8f 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -9,6 +9,35 @@ pub struct Config { pub log: Log, } +impl Default for Config { + fn default() -> Self { + Self { + io: IO { + compression_threshold: 256, + io_worker_threads: 4, + }, + proxy: Proxy {}, + server: Server { + online_mode: true, + motd: "A Feather server".to_string(), + max_players: 256, + view_distance: 6, + address: "126.0.0.1".to_string(), + port: 25565, + }, + gameplay: Gameplay { + monster_spawning: true, + animal_spawning: true, + pvp: true, + nerf_spawner_mobs: false, + }, + log: Log { + level: "debug".to_string(), + }, + } + } +} + #[derive(Deserialize, Debug)] pub struct IO { pub compression_threshold: i32, diff --git a/server/src/entity.rs b/server/src/entity.rs new file mode 100644 index 000000000..c2e25a1ab --- /dev/null +++ b/server/src/entity.rs @@ -0,0 +1,28 @@ +//! Provides several useful components, including `EntityComponent` +//! and `PlayerComponent`. In the future, will also +//! provide entity-specific components and systems. + +use feather_core::world::Position; +use feather_core::Gamemode; +use specs::{Component, DenseVecStorage, VecStorage}; +use uuid::Uuid; + +pub struct PlayerComponent { + pub profile_properties: Vec, + pub gamemode: Gamemode, +} + +impl Component for PlayerComponent { + type Storage = DenseVecStorage; +} + +pub struct EntityComponent { + pub uuid: Uuid, + pub display_name: String, + pub position: Position, + pub on_ground: bool, +} + +impl Component for EntityComponent { + type Storage = VecStorage; +} diff --git a/server/src/genindex.rs b/server/src/genindex.rs deleted file mode 100644 index c65b7ea78..000000000 --- a/server/src/genindex.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::ops::{Index, IndexMut}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct GenerationalIndex { - index: usize, - generation: u32, -} - -impl GenerationalIndex { - pub fn index(&self) -> usize { - self.index - } -} - -struct AllocatorEntry { - is_live: bool, - generation: u32, -} - -pub struct GenerationalIndexAllocator { - entries: Vec, - free: Vec, -} - -impl GenerationalIndexAllocator { - pub fn new() -> Self { - Self { - entries: vec![], - free: vec![], - } - } - pub fn allocate(&mut self) -> GenerationalIndex { - if self.free.is_empty() { - self.entries.push(AllocatorEntry { - is_live: true, - generation: 0, - }); - - GenerationalIndex { - index: self.entries.len() - 1, - generation: 0, - } - } else { - let index = self.free.pop().unwrap(); - let entry = &mut self.entries[index]; - - entry.is_live = true; - entry.generation += 1; - - GenerationalIndex { - index, - generation: entry.generation, - } - } - } - - pub fn deallocate(&mut self, genindex: GenerationalIndex) { - let index = genindex.index; - - assert_eq!(self.entries[index].generation, genindex.generation); - - self.free.push(index); - self.entries[index].is_live = false; - } -} - -struct ArrayEntry { - value: T, - generation: u32, -} - -pub struct GenerationalArray(Vec>>); - -impl GenerationalArray { - pub fn new() -> Self { - Self(vec![]) - } - - pub fn set(&mut self, index: GenerationalIndex, value: T) { - while self.0.len() <= index.index() { - self.0.push(None); - } - - self.0[index.index()] = Some(ArrayEntry { - value, - generation: index.generation, - }); - } - - pub fn remove(&mut self, index: GenerationalIndex) -> Option { - let entry = &mut self.0[index.index()]; - if let Some(entry) = entry { - if entry.generation == index.generation { - let entry = self.0.get_mut(index.index()).unwrap().take().unwrap(); - return Some(entry.value); - } - } - - None - } - - pub fn get(&self, index: GenerationalIndex) -> Option<&T> { - let entry = self.0.get(index.index()); - - if entry.is_none() { - return None; - } - - let entry = entry.unwrap(); - if let Some(entry) = entry { - if entry.generation != index.generation { - return None; - } - - Some(&entry.value) - } else { - None - } - } - - pub fn get_mut(&mut self, index: GenerationalIndex) -> Option<&mut T> { - let entry = self.0.get_mut(index.index()); - - if entry.is_none() { - return None; - } - - let entry = entry.unwrap(); - if let Some(entry) = entry { - if entry.generation != index.generation { - return None; - } - - Some(&mut entry.value) - } else { - None - } - } -} - -impl Index for GenerationalArray { - type Output = T; - - fn index(&self, index: GenerationalIndex) -> &Self::Output { - self.get(index).unwrap() - } -} - -impl IndexMut for GenerationalArray { - fn index_mut(&mut self, index: GenerationalIndex) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_allocator() { - let mut allocator = GenerationalIndexAllocator::new(); - - let index = allocator.allocate(); - assert_eq!(index.generation, 0); - assert_eq!(index.index(), 0); - - let index2 = allocator.allocate(); - assert_eq!(index2.generation, 0); - assert_eq!(index2.index(), 1); - - allocator.deallocate(index); - let index3 = allocator.allocate(); - assert_eq!(index.index(), index3.index()); - assert_eq!(index3.generation, 1); - } - - #[test] - fn test_array() { - let mut arr = GenerationalArray::new(); - let mut alloc = GenerationalIndexAllocator::new(); - - let index1 = alloc.allocate(); - arr.set(index1, "test"); - assert_eq!(arr.get(index1), Some(&"test")); - - alloc.deallocate(index1); - - let index2 = alloc.allocate(); - arr.set(index2, "test2"); - assert_eq!(arr.get(index2), Some(&"test2")); - assert_eq!(arr.get(index1), None); - } -} diff --git a/server/src/main.rs b/server/src/main.rs index df1ab9406..d0155be0a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,85 +10,27 @@ use std::alloc::System; #[global_allocator] static ALLOC: System = System; +pub mod chunkclient; pub mod chunkworker; pub mod config; -pub mod genindex; +pub mod entity; pub mod initialhandler; pub mod io; pub mod network; pub mod prelude; -use crate::genindex::{GenerationalArray, GenerationalIndex, GenerationalIndexAllocator}; use crate::initialhandler::InitialHandlerComponent; -use crate::io::NetworkIoManager; use crate::network::NetworkComponent; use multimap::MultiMap; use prelude::*; +use specs::{Component, DenseVecStorage, Dispatcher, DispatcherBuilder, VecStorage, World}; use std::thread::sleep; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub const TPS: u64 = 20; pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; - -type EntityMap = GenerationalArray; -type Entity = GenerationalIndex; - -pub struct State { - pub chunk_map: ChunkMap, - pub config: Config, - - pub allocator: GenerationalIndexAllocator, - pub io_manager: NetworkIoManager, - - pub network_components: EntityMap, - pub ih_components: EntityMap, - pub entity_components: EntityMap, - pub player_components: EntityMap, - - /// Difference between players and joined_players: - /// `players` includes all clients connected - /// to the server, including those in the login - /// or status phase, while `joined_players` only - /// includes those who have joined the server (whose - /// state is Play). - pub players: Vec, - pub joined_players: Vec, - - /// Each entity which requires its chunks and chunks - /// around it to be loaded can insert an entry into - /// this map. For example, a player will have - /// an entry for every chunk within the view distance. - /// When a loaded chunk has no more holders, it will - /// be queued for unloading. - pub chunk_holders: MultiMap, - - pub chunk_worker_handle: ChunkWorkerHandle, - - pub running: bool, - pub tick_count: u64, -} - -/// A handle for interacting -/// with the chunk worker IO thread. -pub struct ChunkWorkerHandle { - /// Channel used to send chunk requests - sender: crossbeam::channel::Sender, - /// Channel used to receive replies from the worker thread - receiver: crossbeam::channel::Receiver, -} - -pub struct PlayerComponent { - pub profile_properties: Vec, - pub gamemode: Gamemode, -} - -pub struct EntityComponent { - pub uuid: Uuid, - pub display_name: String, - pub position: Position, - pub on_ground: bool, -} +pub const TICK_TIME: u64 = 1000 / TPS; fn main() { let config = config::load_from_file("feather.toml") @@ -103,77 +45,58 @@ fn main() { config.io.io_worker_threads, ); - let mut state = init_state(config, io_manager); + let (mut world, mut dispatcher) = init_world(config, io_manager); - info!("Initialized server state"); + info!("Initialized world"); - run_loop(&mut state); - - state.io_manager.stop(); - state - .chunk_worker_handle - .sender - .send(chunkworker::Request::ShutDown) - .unwrap(); + info!("Server started"); + run_loop(&mut world, &mut dispatcher); } -fn run_loop(state: &mut State) { - while state.running { - // For optimal latency, the chunk worker system - // should run before all other systems - chunk_worker_system(state); - network::network_system(state); +/// Runs the server loop, blocking until the server +/// is shut down. +fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { + loop { + let start_time = current_time_in_millis(); - state.tick_count += 1; - sleep(Duration::from_millis(1000 / 20)); // TODO - proper game loop - } -} + dispatcher.dispatch(world); + + // Sleep correct amount + let end_time = current_time_in_millis(); + let elapsed = start_time - end_time; + if elapsed > TICK_TIME { + continue; // Behind - start next tick immediately + } -/// System for emptying queue of loaded chunks. -fn chunk_worker_system(state: &mut State) { - // Receive all replies from chunk worker thread and load into world - while let Ok((pos, result)) = state.chunk_worker_handle.receiver.try_recv() { - match result { - Ok(chunk) => { - state.chunk_map.inner_mut().insert(pos, chunk); - debug!("Loaded chunk at {:?}", pos); - } - Err(e) => { - // TODO generate new chunk if it wasn't found - warn!("Error occurred while loading chunk at {:?}: {:?}", pos, e); - } + // Sleep in 1ms increments until we've slept enough + let mut sleep_time = (TICK_TIME - elapsed) as i64; + let mut last_sleep_time = current_time_in_millis(); + while sleep_time > 0 { + std::thread::sleep(Duration::from_millis(1)); + sleep_time -= (current_time_in_millis() - last_sleep_time) as i64; + last_sleep_time = current_time_in_millis(); } } } -fn init_state(config: Config, io_manager: NetworkIoManager) -> State { - info!("Starting chunk worker thread"); - let (tx, rx) = chunkworker::start("world"); - State { - chunk_map: ChunkMap::new(), - config, - - allocator: GenerationalIndexAllocator::new(), - io_manager, - - network_components: EntityMap::new(), - ih_components: EntityMap::new(), - entity_components: EntityMap::new(), - player_components: EntityMap::new(), +/// Starts the IO threads. +fn init_io_manager(config: &Config) -> io::NetworkIoManager { + let ioman = + io::NetworkIoManager::start(config.server.addresss.into(), config.io.io_worker_threads); + ioman +} - players: vec![], - joined_players: vec![], +/// Initializes the Specs world. +fn init_world(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher) { + let mut world = World::new(); - chunk_holders: MultiMap::new(), + let mut dispatcher = DispatcherBuilder::new() + .with(chunkclient::ChunkSystem, "chunk", &[]) + .build(); - chunk_worker_handle: ChunkWorkerHandle { - sender: tx, - receiver: rx, - }, + dispatcher.setup(&mut world); - running: true, - tick_count: 0, - } + (world, dispatcher) } fn init_log(config: &Config) { @@ -189,53 +112,33 @@ fn init_log(config: &Config) { simple_logger::init_with_level(level).unwrap(); } -pub fn add_entity(state: &mut State) -> Entity { - let e = state.allocator.allocate(); - e -} - -pub fn add_player(state: &mut State) -> Entity { - let e = add_entity(state); - state.players.push(e); - e +/// Retrieves the current time in seconds +/// since the UNIX epoch. +pub fn current_time_in_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() } -pub fn remove_entity(state: &mut State, entity: Entity) { - state.allocator.deallocate(entity); +/// Retrieves the current time in milleseconds +/// since the UNIX epoch. +pub fn current_time_in_millis() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64 } -pub fn remove_player(state: &mut State, entity: Entity) { - // If player was joined, broadcast disconnect - if state.joined_players.contains(&entity) { - network::broadcast_player_leave(state, entity); - } +#[cfg(test)] +mod tests { + use super::*; - network::handle_player_remove(state, entity); + #[test] + fn test_init_world() { + let config = Default::default(); + let ioman = init_io_manager(&config); - remove_entity(state, entity); - - state.joined_players.retain(|e| *e != entity); - state.players.retain(|e| *e != entity); -} - -/// Asynchronously loads the chunk at the given position. -/// At some point in time after this function is called, -/// the chunk will appear in the chunk map. -/// -/// In the event that the requested chunk does not exist -/// in the world save, it will be generated asynchronously. -pub fn load_chunk(state: &mut State, pos: ChunkPosition) { - // Send request to chunk worker thread - state - .chunk_worker_handle - .sender - .send(chunkworker::Request::LoadChunk(pos)) - .unwrap(); -} - -pub fn current_time_in_secs() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() + let (world, dispatcher) = init_world(config, ioman); + } } From 391920e5702154019237d3cd3c2aa479c33dc6c2 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sun, 21 Jul 2019 16:09:25 -0600 Subject: [PATCH 02/30] Put more work into Specs refactor --- core/src/network/packet/mod.rs | 290 +++++++++++++++++---------------- server/src/entity.rs | 5 +- server/src/initialhandler.rs | 203 +++++++++++++++-------- server/src/main.rs | 49 +++++- server/src/network.rs | 94 +++++++++-- 5 files changed, 412 insertions(+), 229 deletions(-) diff --git a/core/src/network/packet/mod.rs b/core/src/network/packet/mod.rs index db54bcd2b..96330ab94 100644 --- a/core/src/network/packet/mod.rs +++ b/core/src/network/packet/mod.rs @@ -41,61 +41,61 @@ pub enum PacketType { // Serverbound // Handshake - Handshake, + Handshake = 1, // Login - LoginStart, - EncryptionResponse, - LoginPluginResponse, + LoginStart = 2, + EncryptionResponse = 3, + LoginPluginResponse = 4, // Play - TeleportConfirm, - QueryBlockNBT, - ChatMessageServerbound, - ClientStatus, - ClientSettings, - TabCompleteServerbound, - ConfirmTransactionServerbound, - EnchantItem, - ClickWindow, - CloseWindowServerbound, - PluginMessageServerbound, - EditBook, - QueryEntityNBT, - UseEntity, - KeepAliveServerbound, - Player, - PlayerPosition, - PlayerPositionAndLookServerbound, - PlayerLook, - VehicleMoveServerbound, - SteerBoat, - PickItem, - CraftRecipeRequest, - PlayerAbilitiesServerbound, - PlayerDigging, - EntityAction, - SteerVehicle, - RecipeBookData, - NameItem, - ResourcePackStatus, - AdvancementTab, - SelectTrade, - SetBeaconEffect, - HeldItemChangeServerbound, - UpdateCommandBlock, - UpdateCommandBlockMinecart, - CreativeInventoryAction, - UpdateStructureBlock, - UpdateSign, - AnimationServerbound, - Spectate, - PlayerBlockPlacement, - UseItem, + TeleportConfirm = 5, + QueryBlockNBT = 6, + ChatMessageServerbound = 7, + ClientStatus = 8, + ClientSettings = 9, + TabCompleteServerbound = 10, + ConfirmTransactionServerbound = 11, + EnchantItem = 12, + ClickWindow = 13, + CloseWindowServerbound = 14, + PluginMessageServerbound = 15, + EditBook = 16, + QueryEntityNBT = 17, + UseEntity = 18, + KeepAliveServerbound = 19, + Player = 20, + PlayerPosition = 21, + PlayerPositionAndLookServerbound = 22, + PlayerLook = 23, + VehicleMoveServerbound = 24, + SteerBoat = 25, + PickItem = 26, + CraftRecipeRequest = 27, + PlayerAbilitiesServerbound = 28, + PlayerDigging = 29, + EntityAction = 30, + SteerVehicle = 31, + RecipeBookData = 32, + NameItem = 33, + ResourcePackStatus = 34, + AdvancementTab = 35, + SelectTrade = 36, + SetBeaconEffect = 37, + HeldItemChangeServerbound = 38, + UpdateCommandBlock = 39, + UpdateCommandBlockMinecart = 40, + CreativeInventoryAction = 41, + UpdateStructureBlock = 42, + UpdateSign = 43, + AnimationServerbound = 44, + Spectate = 45, + PlayerBlockPlacement = 46, + UseItem = 47, // Status - Request, - Ping, + Request = 48, + Ping = 49, // Clientbound @@ -103,103 +103,103 @@ pub enum PacketType { // (none) // Login - DisconnectLogin, - EncryptionRequest, - LoginSuccess, - SetCompression, - LoginPluginRequest, + DisconnectLogin = 50, + EncryptionRequest = 51, + LoginSuccess = 52, + SetCompression = 53, + LoginPluginRequest = 54, // Play - SpawnObject, - SpawnExperienceOrb, - SpawnGlobalOrb, - SpawnGlobalEntity, - SpawnMob, - SpawnPainting, - SpawnPlayer, - AnimationClientbound, - Statistics, - BlockBreakAnimation, - UpdateBlockEntity, - BlockAction, - BlockChange, - BossBar, - ServerDifficulty, - ChatMessageClientbound, - MultiBlockChange, - TabCompleteClientbound, - DeclareCommands, - ConfirmTransactionClientbound, - CloseWindowClientbound, - OpenWindow, - WindowItems, - WindowProperty, - SetSlot, - SetCooldown, - PluginMessageClientbound, - NamedSoundEffect, - DisconnectPlay, - EntityStatus, - NBTQueryResponse, - Explosion, - UnloadChunk, - ChangeGameState, - KeepAliveClientbound, - ChunkData, - Effect, - Particle, - JoinGame, - MapData, - Entity, - EntityRelativeMove, - EntityLookAndRelativeMove, - EntityLook, - VehicleMoveClientbound, - OpenSignEditor, - CraftRecipeResponse, - PlayerAbilitiesClientbound, - CombatEvent, - PlayerInfo, - FacePlayer, - PlayerPositionAndLookClientbound, - UseBed, - UnlockRecipes, - DestroyEntities, - RemoveEntityEffect, - ResourcePackSend, - Respawn, - EntityHeadLook, - SelectAdvancementTab, - WorldBorder, - Camera, - HeldItemChangeClientbound, - DisplayScoreboard, - EntityMetadata, - AttachEntity, - EntityVelocity, - EntityEquipment, - SetExperience, - UpdateHealth, - ScoreboardObjective, - SetPassengers, - Teams, - UpdateScore, - SpawnPosition, - TimeUpdate, - StopSound, - SoundEffect, - PlayerListHeaderAndFooter, - CollectItem, - EntityTeleport, - Advancements, - EntityProperties, - EntityEffect, - DeclareRecipes, - Tags, + SpawnObject = 55, + SpawnExperienceOrb = 56, + SpawnGlobalOrb = 57, + SpawnGlobalEntity = 58, + SpawnMob = 59, + SpawnPainting = 60, + SpawnPlayer = 61, + AnimationClientbound = 62, + Statistics = 63, + BlockBreakAnimation = 64, + UpdateBlockEntity = 65, + BlockAction = 66, + BlockChange = 67, + BossBar = 68, + ServerDifficulty = 69, + ChatMessageClientbound = 70, + MultiBlockChange = 71, + TabCompleteClientbound = 72, + DeclareCommands = 73, + ConfirmTransactionClientbound = 74, + CloseWindowClientbound = 75, + OpenWindow = 76, + WindowItems = 77, + WindowProperty = 78, + SetSlot = 79, + SetCooldown = 80, + PluginMessageClientbound = 81, + NamedSoundEffect = 82, + DisconnectPlay = 83, + EntityStatus = 84, + NBTQueryResponse = 85, + Explosion = 86, + UnloadChunk = 87, + ChangeGameState = 88, + KeepAliveClientbound = 89, + ChunkData = 90, + Effect = 91, + Particle = 92, + JoinGame = 93, + MapData = 94, + Entity = 95, + EntityRelativeMove = 96, + EntityLookAndRelativeMove = 97, + EntityLook = 98, + VehicleMoveClientbound = 99, + OpenSignEditor = 100, + CraftRecipeResponse = 101, + PlayerAbilitiesClientbound = 102, + CombatEvent = 103, + PlayerInfo = 104, + FacePlayer = 105, + PlayerPositionAndLookClientbound = 106, + UseBed = 107, + UnlockRecipes = 108, + DestroyEntities = 109, + RemoveEntityEffect = 110, + ResourcePackSend = 111, + Respawn = 112, + EntityHeadLook = 113, + SelectAdvancementTab = 114, + WorldBorder = 115, + Camera = 116, + HeldItemChangeClientbound = 117, + DisplayScoreboard = 118, + EntityMetadata = 119, + AttachEntity = 120, + EntityVelocity = 121, + EntityEquipment = 122, + SetExperience = 123, + UpdateHealth = 124, + ScoreboardObjective = 125, + SetPassengers = 126, + Teams = 127, + UpdateScore = 128, + SpawnPosition = 129, + TimeUpdate = 130, + StopSound = 131, + SoundEffect = 132, + PlayerListHeaderAndFooter = 133, + CollectItem = 134, + EntityTeleport = 135, + Advancements = 136, + EntityProperties = 137, + EntityEffect = 138, + DeclareRecipes = 139, + Tags = 140, // Status - Response, - Pong, + Response = 141, + Pong = 142, } lazy_static! { @@ -527,6 +527,12 @@ impl PacketType { pub fn get_implementation(&self) -> Box { implementation::IMPL_MAP.get(self).unwrap().build() } + + /// Returns a unique ID, allocated + /// consecutively for each packet type. + pub fn ordinal(&self) -> usize { + self as usize + } } /// Certain packets have the same ID as diff --git a/server/src/entity.rs b/server/src/entity.rs index c2e25a1ab..f9106a8bb 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -4,7 +4,8 @@ use feather_core::world::Position; use feather_core::Gamemode; -use specs::{Component, DenseVecStorage, VecStorage}; +use specs::storage::BTreeStorage; +use specs::{Component, VecStorage}; use uuid::Uuid; pub struct PlayerComponent { @@ -13,7 +14,7 @@ pub struct PlayerComponent { } impl Component for PlayerComponent { - type Storage = DenseVecStorage; + type Storage = BTreeStorage; } pub struct EntityComponent { diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index fee38c8e3..a656379c9 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -1,9 +1,10 @@ +use crate::entity::{EntityComponent, PlayerComponent}; use crate::network::{ broadcast_player_join, enable_compression_for_player, enable_encryption_for_player, - get_player_initialization_packets, send_packet_to_player, + get_player_initialization_packets, send_packet_to_player, NetworkComponent, + PacketQueueComponent, }; use crate::prelude::*; -use crate::{load_chunk, remove_player, Entity, EntityComponent, PlayerComponent, State}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use openssl::pkey::Private; use openssl::rsa::{Padding, Rsa}; @@ -11,7 +12,12 @@ use std::fmt::Formatter; use std::str::FromStr; use super::{PROTOCOL_VERSION, SERVER_VERSION}; +use crate::{disconnect_player, PlayerCount}; use mojang_api::ServerAuthResponse; +use specs::{ + Component, Entities, Entity, HashMapStorage, LazyUpdate, Read, ReadStorage, System, Write, + WriteStorage, +}; const RSA_BITS: u32 = 1024; // Yes, very secure @@ -33,12 +39,16 @@ pub struct InitialHandlerComponent { pub stage: Stage, rsa_key: Option>, verify_token: [u8; VERIFY_TOKEN_LEN], - /// Sent in Login Start + /// Sent to server in Login Start username: Option, } -impl InitialHandlerComponent { - pub fn new() -> Self { +impl Component for InitialHandlerComponent { + type Storage = HashMapStorage; +} + +impl Default for InitialHandlerComponent { + fn default() -> Self { Self { stage: Stage::AwaitHandshake, rsa_key: None, @@ -48,7 +58,77 @@ impl InitialHandlerComponent { } } -pub enum Error { +/// System for handling the login sequence. +pub struct InitialHandlerSystem; + +impl<'a> System for InitialHandlerSystem { + type SystemData = ( + Entities<'a>, + WriteStorage<'a, IntitialHandlerComponent>, + ReadStorage<'a, NetworkComponent>, + ReadStorage<'a, PacketQueueComponent>, + Read<'a, Config>, + Write<'a, PlayerCount>, + // LazyUpdate is used to add player + entity + // components when a player joins. This prevents + // other systems from being blocked by the initial + // handler because of a dependency on WriteStorage. + Read<'a, LazyUpdate>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (entities, ih_comps, net_comps, packet_queue, config, player_count, lazy) = data; + + let mut packets = vec![]; + packets.append(packet_queue.for_packet(PacketType::Handshake)); + packets.append(packet_queue.for_packet(PacketType::LoginStart)); + packets.append(packet_queue.for_packet(PacketType::Request)); + packets.append(packet_queue.for_packet(PacketType::Ping)); + packets.append(packet_queue.for_packet(PacketType::EncryptionResponse)); + + for (player, packet) in packets { + if !player_check(player, &ih_comps, &lazy) { + continue; // Bad player - kicked + } + + if let Err(e) = handle_packet( + player, + packet, + &entities, + &ih_comps, + &net_comps, + &config, + &player_count, + &lazy, + ) { + disconnect_player( + player, + &format!("InitialHandler: error occurred: {}", e), + &lazy, + ); + info!("InitialHandler: player was kicked: {}", e); + } + } + } +} + +/// Verifies that the given player is still in the login phase, +/// disconnecting them and returning `false` if they are not. +fn player_check( + player: Entity, + ih_comps: &WriteStorage, + lazy: &LazyUpdate, +) -> bool { + if !ih_comps.contains(player) { + disconnect_player(player, "Sent invalid packet while in play", lazy); + return false; + } + true +} + +/// An eror which occurred during initial handling +/// for a player +enum Error { InvalidPacket(PacketType), MalformedData(String), InvalidProtocolVersion(u32, u32), @@ -77,28 +157,8 @@ impl std::fmt::Display for Error { } } -pub fn handle_packet(state: &mut State, player: Entity, packet: Box) -> Result<(), Error> { - match packet.ty() { - PacketType::Handshake => { - handle_handshake(state, player, cast_packet::(&packet))? - } - PacketType::Request => handle_request(state, player, cast_packet::(&packet))?, - PacketType::Ping => handle_ping(state, player, cast_packet::(&packet))?, - PacketType::LoginStart => { - handle_login_start(state, player, cast_packet::(&packet))? - } - PacketType::EncryptionResponse => { - handle_encryption_response(state, player, cast_packet::(&packet))? - } - _ => return Err(Error::InvalidPacket(packet.ty())), - } - Ok(()) -} - -fn handle_handshake(state: &mut State, player: Entity, packet: &Handshake) -> Result<(), Error> { - let ih = state.ih_components.get_mut(player).unwrap(); - - if ih.stage != Stage::AwaitHandshake { +fn handle_handshake(comp: &mut InitialHandlerComponent, packet: &Handshake) -> Result<(), Error> { + if comp.stage != Stage::AwaitHandshake { return Err(Error::InvalidPacket(PacketType::Handshake)); } @@ -110,17 +170,20 @@ fn handle_handshake(state: &mut State, player: Entity, packet: &Handshake) -> Re } match packet.next_state { - HandshakeState::Login => ih.stage = Stage::AwaitLoginStart, - HandshakeState::Status => ih.stage = Stage::AwaitRequest, + HandshakeState::Login => comp.stage = Stage::AwaitLoginStart, + HandshakeState::Status => comp.stage = Stage::AwaitRequest, } Ok(()) } -fn handle_request(state: &mut State, player: Entity, _packet: &Request) -> Result<(), Error> { - let ih = state.ih_components.get_mut(player).unwrap(); - - if ih.stage != Stage::AwaitRequest { +fn handle_request( + comp: &mut InitialHandlerComponent, + config: &Config, + player_count: &PlayerCount, + packet: &Request, +) -> Result<(), Error> { + if comp.stage != Stage::AwaitRequest { return Err(Error::InvalidPacket(PacketType::Request)); } @@ -130,48 +193,56 @@ fn handle_request(state: &mut State, player: Entity, _packet: &Request) -> Resul "protocol": PROTOCOL_VERSION, }, "players": { - "max": state.config.server.max_players, - "online": state.joined_players.len(), + "max": config.server.max_players, + "online": joined_players.len(), }, - "description": state.config.server.motd, + "description": config.server.motd, }); let response = Response::new(payload.to_string()); - ih.stage = Stage::AwaitPing; + comp.stage = Stage::AwaitPing; send_packet_to_player(state, player, response); Ok(()) } -fn handle_ping(state: &mut State, player: Entity, packet: &Ping) -> Result<(), Error> { - let ih = state.ih_components.get_mut(player).unwrap(); - - if ih.stage != Stage::AwaitPing { +fn handle_ping( + comp: &mut InitialHandlerComponent, + player: Entity, + entities: Entities, + packet: &Ping, +) -> Result<(), Error> { + if comp.stage != Stage::AwaitPing { return Err(Error::InvalidPacket(PacketType::Ping)); } let pong = Pong::new(packet.payload); send_packet_to_player(state, player, pong); - remove_player(state, player); + entities.delete(player); debug!("Status handling success"); Ok(()) } -fn handle_login_start(state: &mut State, player: Entity, packet: &LoginStart) -> Result<(), Error> { - state.ih_components[player].username = Some(packet.username.clone()); - - let ih = state.ih_components.get(player).unwrap(); +fn handle_login_start<'a>( + comp: &mut IntiialHandlerComponent, + player: Entity, + config: &Config, + player_comps: &mut WriteStorage<'a, PlayerComponent>, + entity_comps: &mut WriteStorage<'a, EntityComponent>, + packet: &LoginStart, +) -> Result<(), Error> { + comp.username = Some(packet.username.clone()); - if ih.stage != Stage::AwaitLoginStart { + if comp.stage != Stage::AwaitLoginStart { return Err(Error::InvalidPacket(PacketType::Ping)); } // If in online mode, enable encryption - if state.config.server.online_mode { + if config.server.online_mode { let rsa_key = Rsa::generate(RSA_BITS).unwrap(); let key_bytes = rsa_key.public_key_to_der().unwrap(); @@ -187,8 +258,8 @@ fn handle_login_start(state: &mut State, player: Entity, packet: &LoginStart) -> verify_token, ); - state.ih_components[player].rsa_key = Some(rsa_key); - state.ih_components[player].stage = Stage::AwaitEncryptionResponse; + comp.rsa_key = Some(rsa_key); + comp.stage = Stage::AwaitEncryptionResponse; send_packet_to_player(state, player, encryption_request); } else { @@ -198,7 +269,7 @@ fn handle_login_start(state: &mut State, player: Entity, packet: &LoginStart) -> profile_properties: vec![], gamemode: Gamemode::Creative, }; - state.player_components.set(player, player_comp); + player_comps.insert(player, player_comp); let entity_comp = EntityComponent { position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), @@ -206,7 +277,7 @@ fn handle_login_start(state: &mut State, player: Entity, packet: &LoginStart) -> display_name: ih.username.as_ref().unwrap().clone(), on_ground: true, }; - state.entity_components.set(player, entity_comp); + entity_comps.insert(player, entity_comp); finish(state, player); } @@ -214,18 +285,19 @@ fn handle_login_start(state: &mut State, player: Entity, packet: &LoginStart) -> Ok(()) } -fn handle_encryption_response( - state: &mut State, +fn handle_encryption_response<'a>( + comp: &mut IntiialHandlerComponent, player: Entity, + config: &Config, + player_comps: &mut WriteStorage<'a, PlayerComponent>, + entity_comps: &mut WriteStorage<'a, EntityComponent>, packet: &EncryptionResponse, ) -> Result<(), Error> { - let ih = state.ih_components.get(player).unwrap(); - - if ih.stage != Stage::AwaitEncryptionResponse { + if comp.stage != Stage::AwaitEncryptionResponse { return Err(Error::InvalidPacket(PacketType::Ping)); } - let rsa = ih.rsa_key.as_ref().unwrap(); + let rsa = comp.rsa_key.as_ref().unwrap(); let secret: [u8; 16] = { let mut buf = vec![0u8; rsa.size() as usize]; @@ -282,15 +354,15 @@ fn handle_encryption_response( // Authenticate let auth_res = authenticate( secret.clone(), - &ih.rsa_key.as_ref().unwrap().public_key_to_der().unwrap(), - ih.username.as_ref().unwrap(), + &comp.rsa_key.as_ref().unwrap().public_key_to_der().unwrap(), + comp.username.as_ref().unwrap(), ); if let Ok(res) = auth_res { let player_comp = PlayerComponent { profile_properties: res.properties, gamemode: Gamemode::Creative, }; - state.player_components.set(player, player_comp); + player_comps.insert(player, player_comp); let entity_comp = EntityComponent { position: Position::new(0.0, 64.0, 0.0, 0.0, 0.0), @@ -298,7 +370,7 @@ fn handle_encryption_response( display_name: ih.username.as_ref().unwrap().clone(), on_ground: true, }; - state.entity_components.set(player, entity_comp); + entity_comps.insert(player, entity_comp); debug!("Authentication successful"); } else { return Err(Error::AuthenticationFailed); @@ -311,9 +383,9 @@ fn handle_encryption_response( Ok(()) } -fn finish(state: &mut State, player: Entity) { +fn finish(player: Entity, config: &Config) { // Enable compression if needed - let threshold = state.config.io.compression_threshold; + let threshold = config.io.compression_threshold; if threshold > 0 { let set_compression = SetCompression::new(threshold); send_packet_to_player(state, player, set_compression); @@ -335,6 +407,7 @@ fn finish(state: &mut State, player: Entity) { fn join_game(state: &mut State, player: Entity) { let join_game = JoinGame::new( player.index() as i32, + is: open, Gamemode::Creative.get_id(), Dimension::Overwold.get_id(), Difficulty::Medium.get_id(), diff --git a/server/src/main.rs b/server/src/main.rs index d0155be0a..91e6eaffa 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -19,11 +19,16 @@ pub mod io; pub mod network; pub mod prelude; +use crate::entity::EntityComponent; use crate::initialhandler::InitialHandlerComponent; -use crate::network::NetworkComponent; +use crate::network::{send_packet_to_player, NetworkComponent}; +use feather_core::network::packet::PacketType::DisconnectPlay; use multimap::MultiMap; use prelude::*; -use specs::{Component, DenseVecStorage, Dispatcher, DispatcherBuilder, VecStorage, World}; +use specs::{ + Component, DenseVecStorage, Dispatcher, DispatcherBuilder, Entity, LazyUpdate, VecStorage, + World, WorldExt, +}; use std::thread::sleep; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -32,6 +37,8 @@ pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; pub const TICK_TIME: u64 = 1000 / TPS; +pub struct PlayerCount(u32); + fn main() { let config = config::load_from_file("feather.toml") .expect("Failed to load configuration. Please ensure that the file feather.toml exists and is correct."); @@ -60,6 +67,7 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { let start_time = current_time_in_millis(); dispatcher.dispatch(world); + world.maintain(); // Sleep correct amount let end_time = current_time_in_millis(); @@ -89,9 +97,27 @@ fn init_io_manager(config: &Config) -> io::NetworkIoManager { /// Initializes the Specs world. fn init_world(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher) { let mut world = World::new(); + world.insert(config); + world.insert(ioman); let mut dispatcher = DispatcherBuilder::new() - .with(chunkclient::ChunkSystem, "chunk", &[]) + .with(chunkclient::ChunkSystem, "chunk_load", &[]) + .with(network::NetworkSystem, "network", &[]) + .with( + initialhandler::InitialHandlerSystem, + "initial_handler", + &["network", "chunk_load"], + ) + .with( + worldupdate::WorldUpdateSystem, + "world_update", + &["network", "chunk_load"], + ) + .with( + entity::PlayerUpdateSystem, + "player_update", + &["network", "chunk_load"], + ) .build(); dispatcher.setup(&mut world); @@ -130,6 +156,23 @@ pub fn current_time_in_millis() -> u64 { .as_millis() as u64 } +/// Disconnects the given player, removing them from the world. +/// This operation is performed lazily. +pub fn disconnect_player(player: Entity, reason: &str, lazy: &LazyUpdate) { + lazy.exec_mut(|world| { + let packet = DisconnectPlay::new(reason.to_string()); + send_packet_to_player(world.read_component().get(player), packet); + + network::broadcast_player_leave(world.read_component(), player); + + if let Some(ecomp) = world.read_component::().get(player) { + info!("Disconnected player {}: {}", ecomp.display_name, reason); + } + + world.delete_entity(player).unwrap(); + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/server/src/network.rs b/server/src/network.rs index 50f3fb94a..2cbed7f17 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,14 +1,66 @@ +use crate::entity::PlayerComponent; +use crate::initialhandler as ih; use crate::initialhandler::InitialHandlerComponent; use crate::io::{ServerToListenerMessage, ServerToWorkerMessage}; use crate::prelude::*; -use crate::{add_player, initialhandler as ih, remove_player, Entity, State}; use feather_blocks::Block; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; +use specs::{Entities, Entity, ReadStorage}; +use std::sync::Mutex; //const MAX_KEEP_ALIVE_TIME: u64 = 30; //const HEAD_OFFSET: f64 = 1.62; // Offset from feet pos to head pos +/// A component which contains the received packets +/// for this tick. +pub struct PacketQueueComponent { + queue: Mutex)>>>, +} + +impl PacketQueueComponent { + /// Returns the packets queued for handling + /// of the given type, draining the queue of this + /// type of packet. + pub fn for_packet(&self, ty: PacketType) -> Vec<(Entity, Box)> { + let mut queue = self.queue.lock().unwrap(); + + let ordinal = ty.ordinal(); + if ordinal >= queue.len() { + self.expand(&mut queue, ordinal); + } + + let mut result = vec![]; + std::mem::swap(&mut result, queue.get_mut(ordinal).unwrap()); + + result + } + + fn expand( + &self, + queue: &mut std::sync::MutexGuard)>>>, + to: usize, + ) { + if to < queue.len() { + return; + } + + for _ in queue.len()..(to + 1) { + queue.push(Vec::new()); + } + } + + fn add_for_packet(&self, player: Entity, packet: Box) { + let mut queue = self.queue.lock().unwrap(); + + let ordinal = packet.ty().ordinal(); + if ordinal >= queue.len() { + self.expand(&mut queue, ordinal); + } + + self.queue[ordinal].push((player, packet)); + } +} pub struct NetworkComponent { sender: Sender, @@ -231,18 +283,6 @@ fn poll_for_new_players(state: &mut State) { } } -pub fn send_packet_to_player(state: &State, player: Entity, packet: P) { - let comp = &state.network_components[player]; - let _ = comp - .sender - .send(ServerToWorkerMessage::SendPacket(Box::new(packet))); -} - -pub fn send_packet_boxed_to_player(state: &State, player: Entity, packet: Box) { - let comp = &state.network_components[player]; - let _ = comp.sender.send(ServerToWorkerMessage::SendPacket(packet)); -} - pub fn enable_compression_for_player(state: &State, player: Entity, threshold: usize) { let comp = &state.network_components[player]; let _ = comp @@ -432,18 +472,38 @@ pub fn broadcast_block_update(state: &mut State, pos: BlockPosition) { send_packet_to_all_players(state, packet, None); } +/// Sends a packet to all (joined) players on the server, excluding +/// `neq`, if it exists. fn send_packet_to_all_players( - state: &State, + net_comps: &ReadStorage, + player_comps: &ReadStorage, + entities: &Entities, packet: P, neq: Option, ) { - for player in &state.joined_players { - if neq.is_none() || *player != neq.unwrap() { - send_packet_to_player(state, *player, packet.clone()); + for (entity, net, player) in (entities, net_comps, player_comps).join() { + if let Some(e) = neq.as_ref() { + if e == entity { + continue; // Exclude this entity + } } + + send_packet_to_player(net, packet.clone()); } } +/// Sends a packet to the given player. +pub fn send_packet_to_player(comp: &NetworkComponent, packet: P) { + let _ = comp + .sender + .send(ServerToWorkerMessage::SendPacket(Box::new(packet))); +} + +/// Sends a packet to the given player. +pub fn send_packet_boxed_to_player(comp: &NetworkComponent, packet: Box) { + let _ = comp.sender.send(ServerToWorkerMessage::SendPacket(packet)); +} + pub fn degrees_to_stops(degs: f32) -> u8 { ((degs / 360.0) * 256.) as u8 } From c4d971d9fcb546c6fb18c4099f3127551b24df4c Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 22 Jul 2019 10:30:07 -0600 Subject: [PATCH 03/30] Further work on converting InitialHandler to Specs --- core/src/network/packet/mod.rs | 2 +- server/src/chunkclient.rs | 2 +- server/src/initialhandler.rs | 168 ++++++++++++++++++++++++--------- server/src/network.rs | 45 +++++---- 4 files changed, 149 insertions(+), 68 deletions(-) diff --git a/core/src/network/packet/mod.rs b/core/src/network/packet/mod.rs index 96330ab94..d45ebf0aa 100644 --- a/core/src/network/packet/mod.rs +++ b/core/src/network/packet/mod.rs @@ -531,7 +531,7 @@ impl PacketType { /// Returns a unique ID, allocated /// consecutively for each packet type. pub fn ordinal(&self) -> usize { - self as usize + *self as usize } } diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index 9629a72da..6de479e7b 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -3,7 +3,7 @@ use crate::chunkworker; use crossbeam::channel::{Receiver, Sender}; use feather_core::world::{ChunkMap, ChunkPosition}; -use specs::{prelude::Resources, Read, System, World, Write}; +use specs::{Read, System, World, Write}; pub struct ChunkWorkerHandle { sender: Sender, diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index a656379c9..25c84b819 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -12,11 +12,12 @@ use std::fmt::Formatter; use std::str::FromStr; use super::{PROTOCOL_VERSION, SERVER_VERSION}; +use crate::chunkclient::{load_chunk, ChunkWorkerHandle}; use crate::{disconnect_player, PlayerCount}; use mojang_api::ServerAuthResponse; use specs::{ - Component, Entities, Entity, HashMapStorage, LazyUpdate, Read, ReadStorage, System, Write, - WriteStorage, + Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System, + WorldExt, Write, WriteStorage, }; const RSA_BITS: u32 = 1024; // Yes, very secure @@ -41,6 +42,8 @@ pub struct InitialHandlerComponent { verify_token: [u8; VERIFY_TOKEN_LEN], /// Sent to server in Login Start username: Option, + /// Whether the finish() function should be called on the player + should_finish: bool, } impl Component for InitialHandlerComponent { @@ -54,6 +57,7 @@ impl Default for InitialHandlerComponent { rsa_key: None, verify_token: rand::random(), username: None, + should_finish: false, } } } @@ -77,7 +81,8 @@ impl<'a> System for InitialHandlerSystem { ); fn run(&mut self, data: Self::SystemData) { - let (entities, ih_comps, net_comps, packet_queue, config, player_count, lazy) = data; + let (entities, mut ih_comps, net_comps, packet_queue, config, mut player_count, lazy) = + data; let mut packets = vec![]; packets.append(packet_queue.for_packet(PacketType::Handshake)); @@ -95,10 +100,10 @@ impl<'a> System for InitialHandlerSystem { player, packet, &entities, - &ih_comps, + &mut ih_comps, &net_comps, &config, - &player_count, + &mut player_count, &lazy, ) { disconnect_player( @@ -108,6 +113,10 @@ impl<'a> System for InitialHandlerSystem { ); info!("InitialHandler: player was kicked: {}", e); } + + if ih_comps.get(player).unwrap().should_finish { + finish() + } } } } @@ -157,6 +166,29 @@ impl std::fmt::Display for Error { } } +fn handle_packet( + player: Entity, + packet: Box, + entities: &Entities, + ih_comps: &mut WriteStorage, + net_comps: &ReadStorage, + config: &Config, + player_count: &mut Write, + lazy: &LazyUpdate, +) -> Result<(), Error> { + let ih_comp = ih_comps.get_mut(player).unwrap(); + match packet.ty() { + PacketType::Handshake => handle_handshake(ih_comp, cast_packet::(&packet)), + PacketType::Request => handle_request( + ih_comp, + config, + player_count, + cast_packet::(&packet), + ), + PacketType::Ping => handle_ping, + } +} + fn handle_handshake(comp: &mut InitialHandlerComponent, packet: &Handshake) -> Result<(), Error> { if comp.stage != Stage::AwaitHandshake { return Err(Error::InvalidPacket(PacketType::Handshake)); @@ -179,6 +211,7 @@ fn handle_handshake(comp: &mut InitialHandlerComponent, packet: &Handshake) -> R fn handle_request( comp: &mut InitialHandlerComponent, + net: &NetworkComponent, config: &Config, player_count: &PlayerCount, packet: &Request, @@ -203,15 +236,16 @@ fn handle_request( comp.stage = Stage::AwaitPing; - send_packet_to_player(state, player, response); + send_packet_to_player(net, response); Ok(()) } fn handle_ping( comp: &mut InitialHandlerComponent, + net: &NetworkComponent, player: Entity, - entities: Entities, + lazy: &LazyUpdate, packet: &Ping, ) -> Result<(), Error> { if comp.stage != Stage::AwaitPing { @@ -219,20 +253,20 @@ fn handle_ping( } let pong = Pong::new(packet.payload); - send_packet_to_player(state, player, pong); + send_packet_to_player(net, pong); - entities.delete(player); + lazy.remove(player); debug!("Status handling success"); Ok(()) } fn handle_login_start<'a>( - comp: &mut IntiialHandlerComponent, + comp: &mut InitialHandlerComponent, + net: &NetworkComponent, player: Entity, config: &Config, - player_comps: &mut WriteStorage<'a, PlayerComponent>, - entity_comps: &mut WriteStorage<'a, EntityComponent>, + lazy: &LazyUpdate, packet: &LoginStart, ) -> Result<(), Error> { comp.username = Some(packet.username.clone()); @@ -261,7 +295,7 @@ fn handle_login_start<'a>( comp.rsa_key = Some(rsa_key); comp.stage = Stage::AwaitEncryptionResponse; - send_packet_to_player(state, player, encryption_request); + send_packet_to_player(net, encryption_request); } else { // Login completed - initialize entity and player components // This would otherwise be done in `handle_encryption_response` @@ -269,7 +303,7 @@ fn handle_login_start<'a>( profile_properties: vec![], gamemode: Gamemode::Creative, }; - player_comps.insert(player, player_comp); + lazy.insert(player, player_comp); let entity_comp = EntityComponent { position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), @@ -277,7 +311,7 @@ fn handle_login_start<'a>( display_name: ih.username.as_ref().unwrap().clone(), on_ground: true, }; - entity_comps.insert(player, entity_comp); + lazy.insert(player, entity_comp); finish(state, player); } @@ -286,11 +320,10 @@ fn handle_login_start<'a>( } fn handle_encryption_response<'a>( - comp: &mut IntiialHandlerComponent, + comp: &mut InitialHandlerComponent, player: Entity, config: &Config, - player_comps: &mut WriteStorage<'a, PlayerComponent>, - entity_comps: &mut WriteStorage<'a, EntityComponent>, + lazy: &LazyUpdate, packet: &EncryptionResponse, ) -> Result<(), Error> { if comp.stage != Stage::AwaitEncryptionResponse { @@ -362,7 +395,7 @@ fn handle_encryption_response<'a>( profile_properties: res.properties, gamemode: Gamemode::Creative, }; - player_comps.insert(player, player_comp); + lazy.insert(player, player_comp); let entity_comp = EntityComponent { position: Position::new(0.0, 64.0, 0.0, 0.0, 0.0), @@ -370,7 +403,7 @@ fn handle_encryption_response<'a>( display_name: ih.username.as_ref().unwrap().clone(), on_ground: true, }; - entity_comps.insert(player, entity_comp); + lazy.insert(player, entity_comp); debug!("Authentication successful"); } else { return Err(Error::AuthenticationFailed); @@ -383,12 +416,23 @@ fn handle_encryption_response<'a>( Ok(()) } -fn finish(player: Entity, config: &Config) { +fn finish( + player: Entity, + ihcomps: &mut WriteStorage, + ecomps: &ReadStorage, + pcomps: &ReadStorage, + netcomps: &ReadStorage, + chunk_handle: &ChunkWorkerHandle, + lazy: &LazyUpdate, + chunk_map: &ChunkMap, + config: &Config, +) { + let net = ncomps.get(player).unwrap(); // Enable compression if needed let threshold = config.io.compression_threshold; if threshold > 0 { let set_compression = SetCompression::new(threshold); - send_packet_to_player(state, player, set_compression); + send_packet_to_player(net, set_compression); enable_compression_for_player(state, player, threshold as usize); } @@ -399,12 +443,30 @@ fn finish(player: Entity, config: &Config) { entity_comp.uuid.to_hyphenated_ref().to_string(), entity_comp.display_name.to_string(), ); - send_packet_to_player(state, player, login_success); - - join_game(state, player); + send_packet_to_player(net, login_success); + + join_game( + player, + ihcomps, + ecomps, + pcomps, + netcomps, + chunk_handle, + lazy, + chunk_map, + ); } -fn join_game(state: &mut State, player: Entity) { +fn join_game( + player: Entity, + ihcomps: &mut WriteStorage, + ecomps: &ReadStorage, + pcomps: &ReadStorage, + netcomps: &ReadStorage, + chunk_handle: &ChunkWorkerHandle, + lazy: &LazyUpdate, + chunk_map: &ChunkMap, +) { let join_game = JoinGame::new( player.index() as i32, is: open, @@ -415,7 +477,10 @@ fn join_game(state: &mut State, player: Entity) { "default".to_string(), false, ); - send_packet_to_player(state, player, join_game); + + let net = netcomps.get(player).unwrap(); + + send_packet_to_player(net, join_game); // Send chunk data. If a chunk in the view distance // hasn't been loaded, a load request will be queued. @@ -426,49 +491,60 @@ fn join_game(state: &mut State, player: Entity) { if let Some(chunk) = state.chunk_map.chunk_at(pos) { let chunk_data = ChunkData::new(chunk.clone()); - send_packet_to_player(state, player, chunk_data); + send_packet_to_player(net, chunk_data); } else { // Queue chunk for loading. - load_chunk(state, pos); + load_chunk(handle, pos); // Make sure that the chunk is sent once loaded - state.network_components[player].chunks_to_send.push(pos); + lazy.exec_mut(|world| { + world + .write_component::() + .get_mut(player) + .chunks_to_send + .push(pos); + }); } } } - if state.network_components[player].chunks_to_send.is_empty() { - complete_join_game(state, player); + if net.chunks_to_send.is_empty() { + complete_join_game(player, ihcomps, ecomps, pcomps, netcomps); } else { // If not all chunks have been sent, we need to wait // until they're loaded and sent before spawning the player. // The network system will call `complete_join_game` upon // sending all chunks. - state.ih_components[player].stage = Stage::AwaitChunkLoad; + ihcomps.get_mut(player).unwrap().stage = Stage::AwaitChunkLoad; } } -pub fn complete_join_game(state: &mut State, player: Entity) { +pub fn complete_join_game( + player: Entity, + ihcomps: &mut WriteStorage, + ecomps: &ReadStorage, + pcomps: &ReadStorage, + netcomps: &ReadStorage, +) { // Send spawn position + player position // TODO proper persistence let spawn_position = SpawnPosition::new(BlockPosition::new(0, 64, 0)); - send_packet_to_player(state, player, spawn_position); + send_packet_to_player(net, spawn_position); let pos_and_look = PlayerPositionAndLookClientbound::new(0.0, 64.0, 0.0, 0.0, 0.0, 0, 0); - send_packet_to_player(state, player, pos_and_look); + send_packet_to_player(net, pos_and_look); // Send other players on the server - for other_player in &state.joined_players { - let (player_info, spawn_player) = get_player_initialization_packets(state, *other_player); - send_packet_to_player(state, player, player_info); - send_packet_to_player(state, player, spawn_player); + for (ecomp, pcomp, net) in (ecomps, pcomps, netcomps).join() { + let (player_info, spawn_player) = + get_player_initialization_packets(ecomp, pcomp, *other_player); + send_packet_to_player(net, player_info); + send_packet_to_player(net, spawn_player); } - state.joined_players.push(player); - - state.ih_components.remove(player); + ihcomps.remove(player); - broadcast_player_join(state, player); + broadcast_player_join(player, netcomps, pcomps, ecomps); info!("A player joined the game"); } @@ -491,8 +567,8 @@ fn compare_verify_tokens(x: [u8; VERIFY_TOKEN_LEN], y: [u8; VERIFY_TOKEN_LEN]) - true } -pub fn disconnect_login(state: &mut State, player: Entity, reason: &str) { +pub fn disconnect_login(net: &NetworkComponent, reason: &str) { let packet = DisconnectLogin::new(json!({ "text": reason }).to_string()); - send_packet_to_player(state, player, packet); + send_packet_to_player(net, packet); } diff --git a/server/src/network.rs b/server/src/network.rs index 2cbed7f17..7c4a36da3 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,4 +1,4 @@ -use crate::entity::PlayerComponent; +use crate::entity::{EntityComponent, PlayerComponent}; use crate::initialhandler as ih; use crate::initialhandler::InitialHandlerComponent; use crate::io::{ServerToListenerMessage, ServerToWorkerMessage}; @@ -307,13 +307,20 @@ pub fn handle_player_remove(state: &mut State, player: Entity) { /// whenever a player joins. /// /// This function is currently called by the initial handler. -pub fn broadcast_player_join(state: &mut State, player: Entity) { - let (player_info, spawn_player) = get_player_initialization_packets(state, player); +pub fn broadcast_player_join( + player: Entity, + netcomps: &ReadStorage, + pcomps: &ReadStorage, + ecomps: &ReadStorage, +) { + let ecomp = ecomps.get(player).unwrap(); + let pcomp = pcomps.get(player).unwrap(); + let (player_info, spawn_player) = get_player_initialization_packets(ecomp, pcomp, player); - for p in &state.joined_players { - send_packet_to_player(state, *p, player_info.clone()); + for (net, _) in (netcomps, pcomps).join() { + send_packet_to_player(net, player_info.clone()); if *p != player { - send_packet_to_player(state, *p, spawn_player.clone()); + send_packet_to_player(net, spawn_player.clone()); } } } @@ -321,19 +328,17 @@ pub fn broadcast_player_join(state: &mut State, player: Entity) { /// Returns the player info and spawn player packets /// for the given player. pub fn get_player_initialization_packets( - state: &State, + ecomp: &EntityComponent, + pcomp: &PlayerComponent, player: Entity, ) -> (PlayerInfo, SpawnPlayer) { - let entity_comp = state.entity_components.get(player).unwrap(); - let player_comp = state.player_components.get(player).unwrap(); - let display_name = json!({ - "text": entity_comp.display_name + "text": ecomp.display_name }) .to_string(); let mut props = vec![]; - for prop in player_comp.profile_properties.iter() { + for prop in pcomp.profile_properties.iter() { props.push(( prop.name.clone(), prop.value.clone(), @@ -342,13 +347,13 @@ pub fn get_player_initialization_packets( } let action = PlayerInfoAction::AddPlayer( - entity_comp.display_name.clone(), + ecomp.display_name.clone(), props, Gamemode::Creative, 50, display_name, ); - let player_info = PlayerInfo::new(action, entity_comp.uuid.clone()); + let player_info = PlayerInfo::new(action, ecomp.uuid.clone()); let metadata = EntityMetadata::new().with(&[ (0, MetaEntry::Byte(0)), @@ -371,12 +376,12 @@ pub fn get_player_initialization_packets( let spawn_player = SpawnPlayer::new( player.index() as i32, - entity_comp.uuid.clone(), - entity_comp.position.x, - entity_comp.position.y, - entity_comp.position.z, - degrees_to_stops(entity_comp.position.pitch), - degrees_to_stops(entity_comp.position.yaw), + ecomp.uuid.clone(), + ecomp.position.x, + ecomp.position.y, + ecomp.position.z, + degrees_to_stops(ecomp.position.pitch), + degrees_to_stops(ecomp.position.yaw), metadata, ); From 00a6533c344503dae6f07e5598d0e64ccafc6475 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 22 Jul 2019 10:41:20 -0600 Subject: [PATCH 04/30] Complete InitialHandler conversion --- server/src/initialhandler.rs | 97 +++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 25c84b819..4641250f1 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -42,8 +42,6 @@ pub struct InitialHandlerComponent { verify_token: [u8; VERIFY_TOKEN_LEN], /// Sent to server in Login Start username: Option, - /// Whether the finish() function should be called on the player - should_finish: bool, } impl Component for InitialHandlerComponent { @@ -113,11 +111,9 @@ impl<'a> System for InitialHandlerSystem { ); info!("InitialHandler: player was kicked: {}", e); } - - if ih_comps.get(player).unwrap().should_finish { - finish() - } } + + // TODO send chunks } } @@ -170,23 +166,56 @@ fn handle_packet( player: Entity, packet: Box, entities: &Entities, - ih_comps: &mut WriteStorage, - net_comps: &ReadStorage, + ihcomps: &mut WriteStorage, + netcomps: &ReadStorage, config: &Config, player_count: &mut Write, lazy: &LazyUpdate, ) -> Result<(), Error> { - let ih_comp = ih_comps.get_mut(player).unwrap(); + let ihcomp = ihcomps.get_mut(player).unwrap(); + let netcomp = netcomps.get(player).unwrap(); + match packet.ty() { - PacketType::Handshake => handle_handshake(ih_comp, cast_packet::(&packet)), + PacketType::Handshake => handle_handshake(ihcomp, cast_packet::(&packet))?, PacketType::Request => handle_request( - ih_comp, + ihcomp, + netcomp, config, player_count, cast_packet::(&packet), + )?, + PacketType::Ping => { + handle_ping(ihcomp, netcomp, player, lazy, cast_packet::(&packet))? + } + PacketType::LoginStart => handle_login_start( + netcomp, + config, + player, + ihcomps, + ecomps, + pcomps, + netcomps, + chunk_handle, + chunk_map, + lazy, + cast_packet::(&packet), + ), + PacketType::EncryptionResponse => handle_encryption_response( + player, + config, + ihcomps, + ecomps, + pcomps, + netcomps, + chunk_handle, + lazy, + chunk_map, + cast_packet::(&packet), ), - PacketType::Ping => handle_ping, + _ => panic!("Invalid packet"), } + + Ok(()) } fn handle_handshake(comp: &mut InitialHandlerComponent, packet: &Handshake) -> Result<(), Error> { @@ -262,13 +291,19 @@ fn handle_ping( } fn handle_login_start<'a>( - comp: &mut InitialHandlerComponent, net: &NetworkComponent, - player: Entity, config: &Config, + player: Entity, + ihcomps: &mut WriteStorage, + ecomps: &ReadStorage, + pcomps: &ReadStorage, + netcomps: &ReadStorage, + chunk_handle: &ChunkWorkerHandle, + chunk_map: &ChunkMap, lazy: &LazyUpdate, packet: &LoginStart, ) -> Result<(), Error> { + let comp = ihcomps.get_mut(player).unwrap(); comp.username = Some(packet.username.clone()); if comp.stage != Stage::AwaitLoginStart { @@ -313,19 +348,35 @@ fn handle_login_start<'a>( }; lazy.insert(player, entity_comp); - finish(state, player); + finish( + player, + ihcomps, + ecomps, + pcomps, + netcomps, + chunk_handle, + lazy, + chunk_map, + config, + ); } Ok(()) } -fn handle_encryption_response<'a>( - comp: &mut InitialHandlerComponent, +fn handle_encryption_response( player: Entity, config: &Config, + ihcomps: &mut WriteStorage, + ecomps: &ReadStorage, + pcomps: &ReadStorage, + netcomps: &ReadStorage, + chunk_handle: &ChunkWorkerHandle, lazy: &LazyUpdate, + chunk_map: &ChunkMap, packet: &EncryptionResponse, ) -> Result<(), Error> { + let comp = ihcomps.get_mut(player).unwrap(); if comp.stage != Stage::AwaitEncryptionResponse { return Err(Error::InvalidPacket(PacketType::Ping)); } @@ -411,7 +462,17 @@ fn handle_encryption_response<'a>( enable_encryption_for_player(state, player, secret); - finish(state, player); + finish( + player, + ihcomps, + ecomps, + pcomps, + netcomps, + chunk_handle, + lazy, + chunk_map, + config, + ); Ok(()) } From 082ba75b948f4b51165aba28910d10b9290f0faf Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 22 Jul 2019 14:16:46 -0600 Subject: [PATCH 05/30] Reimplement network system using Specs --- server/src/initialhandler.rs | 12 +- server/src/main.rs | 7 ++ server/src/network.rs | 206 ++++++++++++++--------------------- 3 files changed, 96 insertions(+), 129 deletions(-) diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 4641250f1..48c752e32 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -1,8 +1,7 @@ use crate::entity::{EntityComponent, PlayerComponent}; use crate::network::{ broadcast_player_join, enable_compression_for_player, enable_encryption_for_player, - get_player_initialization_packets, send_packet_to_player, NetworkComponent, - PacketQueueComponent, + get_player_initialization_packets, send_packet_to_player, NetworkComponent, PacketQueue, }; use crate::prelude::*; use feather_core::network::packet::{implementation::*, Packet, PacketType}; @@ -55,11 +54,16 @@ impl Default for InitialHandlerComponent { rsa_key: None, verify_token: rand::random(), username: None, - should_finish: false, } } } +impl InitialHandlerComponent { + pub fn new() -> Self { + Default::default() + } +} + /// System for handling the login sequence. pub struct InitialHandlerSystem; @@ -68,7 +72,7 @@ impl<'a> System for InitialHandlerSystem { Entities<'a>, WriteStorage<'a, IntitialHandlerComponent>, ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, PacketQueueComponent>, + ReadStorage<'a, PacketQueue>, Read<'a, Config>, Write<'a, PlayerCount>, // LazyUpdate is used to add player + entity diff --git a/server/src/main.rs b/server/src/main.rs index 91e6eaffa..561654c80 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -37,7 +37,10 @@ pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; pub const TICK_TIME: u64 = 1000 / TPS; +#[derive(Default)] pub struct PlayerCount(u32); +#[derive(Default)] +pub struct TickCount(u64); fn main() { let config = config::load_from_file("feather.toml") @@ -69,6 +72,9 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { dispatcher.dispatch(world); world.maintain(); + // Increment tick count + *world.write_resource::().0 += 1; + // Sleep correct amount let end_time = current_time_in_millis(); let elapsed = start_time - end_time; @@ -99,6 +105,7 @@ fn init_world(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher let mut world = World::new(); world.insert(config); world.insert(ioman); + world.insert(TickCount::default()); let mut dispatcher = DispatcherBuilder::new() .with(chunkclient::ChunkSystem, "chunk_load", &[]) diff --git a/server/src/network.rs b/server/src/network.rs index 7c4a36da3..6bbdedd9d 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,24 +1,25 @@ use crate::entity::{EntityComponent, PlayerComponent}; -use crate::initialhandler as ih; use crate::initialhandler::InitialHandlerComponent; -use crate::io::{ServerToListenerMessage, ServerToWorkerMessage}; +use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; use crate::prelude::*; +use crate::{initialhandler as ih, TickCount}; use feather_blocks::Block; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; -use specs::{Entities, Entity, ReadStorage}; +use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, Write, WriteStorage}; use std::sync::Mutex; //const MAX_KEEP_ALIVE_TIME: u64 = 30; //const HEAD_OFFSET: f64 = 1.62; // Offset from feet pos to head pos + /// A component which contains the received packets /// for this tick. -pub struct PacketQueueComponent { +pub struct PacketQueue { queue: Mutex)>>>, } -impl PacketQueueComponent { +impl PacketQueue { /// Returns the packets queued for handling /// of the given type, draining the queue of this /// type of packet. @@ -36,6 +37,7 @@ impl PacketQueueComponent { result } + /// Expands the internal vector to allow for additional packet types. fn expand( &self, queue: &mut std::sync::MutexGuard)>>>, @@ -50,6 +52,7 @@ impl PacketQueueComponent { } } + /// Adds a packet to the queue. fn add_for_packet(&self, player: Entity, packet: Box) { let mut queue = self.queue.lock().unwrap(); @@ -62,6 +65,14 @@ impl PacketQueueComponent { } } +impl Default for PacketQueue { + fn default() -> Self { + Self { + queue: Mutex::new(vec![]), + } + } +} + pub struct NetworkComponent { sender: Sender, receiver: Receiver, @@ -85,101 +96,81 @@ impl NetworkComponent { } } -pub fn network_system(state: &mut State) { - handle_connections(state); - - send_keep_alives(state); - - poll_for_new_players(state); -} - -fn handle_connections(state: &mut State) { - let mut players_to_remove = vec![]; +/// The network system, responsible for +/// receiving and buffering packets received +/// from players. Received packets +/// are added to a queue (`PacketQueue`) so that +/// other systems can handle them. +pub struct NetworkSystem; + +impl<'a> System<'a> for NetworkSystem { + type SystemData = ( + WriteStorage<'a, NetworkComponent>, + WriteStorage<'a, InitialHandlerComponent>, + ReadStorage<'a, PlayerComponent>, + Write<'a, PacketQueue>, + Read<'a, NetworkIoManager>, + Entities<'a>, + Read<'a, TickCount>, + Read<'a, LazyUpdate>, + ); - for player in state.players.clone() { - while let Ok(msg) = state.network_components[player].receiver.try_recv() { + fn run(&mut self, data: Self::SystemData) { + let (mut netcomps, mut ihcomps, pcomps, packet_queue, ioman, entities, tick_count, lazy) = + data; + // Poll for new connections + while let Ok(msg) = ioman.receiver.try_recv() { match msg { - ServerToWorkerMessage::NotifyPacketReceived(packet) => { - if let Some(ih) = state.ih_components.get(player) { - // Skip if initial handler is awaiting chunks - if ih.stage != ih::Stage::AwaitChunkLoad { - if let Err(e) = ih::handle_packet(state, player, packet) { - info!("Disconnecting player: {}", e); - ih::disconnect_login(state, player, &e.to_string()); - remove_player(state, player); - } - } - } else { - handle_player_packet(state, player, packet); - } - } - ServerToWorkerMessage::NotifyDisconnect => { - players_to_remove.push(player); + ServerToListenerMessage::NewClient(info) => { + // New connection - handle it + info!("Accepting connection from {}", info.ip); + let netcomp = NetworkComponent::new(info.sender, info.receiver); + + // Create entity + let new_entity = entities.create(); + netcomps.insert(new_entity, netcomp); + + // Create initial handler + let ih = InitialHandlerComponent::new(); + ihcomps.insert(new_entity, ih); } - _ => panic!("Invalid message received from worker thread"), + _ => panic!("Network system received invalid message from IO listener"), } } - // Send all pending chunks which have been loaded - let mut to_remove = vec![]; - for (i, chunk_pos) in state.network_components[player] - .chunks_to_send - .clone() - .iter() - .enumerate() - { - if let Some(chunk) = state.chunk_map.chunk_at(*chunk_pos) { - let packet = ChunkData::new(chunk.clone()); - send_packet_to_player(state, player, packet); - to_remove.push(i); - } else { + // Receive packets + disconnects from players + for (player, netcomp) in (&entities, &netcomps).join() { + while let Ok(msg) = netcomp.receiver.try_recv() { + match msg { + ServerToWorkerMessage::NotifyPacketReceived(packet) => { + packet_queue.add_for_packet(player, packet); + } + ServerToWorkerMessage::NotifyDisconnect => { + // TODO broadcast disconnect + entities.delete(player); + } + msg => panic!( + "Network system received invalid message from IO worker: {:?}", + msg + ), + } } } - let mut count = 0; - for i in to_remove { - state.network_components[player] - .chunks_to_send - .remove(i - count); - count += 1; - } - - // If the player has yet to receive their spawn position, - // send it if all their chunks have been loaded - if state.network_components[player].chunks_to_send.is_empty() - && state.ih_components.get(player).is_some() - && state.ih_components[player].stage == ih::Stage::AwaitChunkLoad - { - ih::complete_join_game(state, player); - } - } - - for _player in players_to_remove { - remove_player(state, _player); - } -} -fn handle_player_packet(state: &mut State, player: Entity, packet: Box) { - match packet.ty() { - PacketType::PlayerPositionAndLookServerbound => handle_player_pos_and_look( - state, - player, - cast_packet::(&packet), - ), - PacketType::PlayerPosition => { - handle_player_pos(state, player, cast_packet::(&packet)); - } - PacketType::PlayerLook => { - handle_player_look(state, player, cast_packet::(&packet)); - } - PacketType::PlayerDigging => { - handle_player_digging(state, player, cast_packet::(&packet)); + // Send keepalives every second. The dependency on the player + // component is required because keepalives should + // only be sent to players who have joined (completed + // the login process). + // TODO check that player hasn't timed out + if tick_count.0 % TPS == 0 { + for (netcomp, _) in (&netcomps, &pcomps).join() { + send_packet_to_player(netcomp, KeepAliveClientbound::new(0)); + } } - _ => (), // TODO } } // TODO proper validation of new position - fn handle_player_pos_and_look( state: &mut State, player: Entity, @@ -255,53 +246,18 @@ fn handle_player_digging(state: &mut State, player: Entity, packet: &PlayerDiggi } } -fn send_keep_alives(state: &mut State) { - if state.tick_count % TPS != 0 { - return; // Only run once per second - } - - for player in state.joined_players.clone() { - let keep_alive = KeepAliveClientbound::new(0); - send_packet_to_player(state, player, keep_alive); - } -} - -fn poll_for_new_players(state: &mut State) { - while let Ok(msg) = state.io_manager.receiver.try_recv() { - match msg { - ServerToListenerMessage::NewClient(info) => { - debug!("Server registering player"); - let player = add_player(state); - let ih = InitialHandlerComponent::new(); - state.ih_components.set(player, ih); - - let netc = NetworkComponent::new(info.sender, info.receiver); - state.network_components.set(player, netc); - } - _ => panic!("Invalid message received from listener thread"), - } - } -} - -pub fn enable_compression_for_player(state: &State, player: Entity, threshold: usize) { - let comp = &state.network_components[player]; - let _ = comp +pub fn enable_compression_for_player(net: &NetworkComponent, threshold: usize) { + let _ = net .sender .send(ServerToWorkerMessage::EnableCompression(threshold)); } -pub fn enable_encryption_for_player(state: &State, player: Entity, key: [u8; 16]) { - let comp = &state.network_components[player]; - let _ = comp +pub fn enable_encryption_for_player(net: &NetworkComponent, key: [u8; 16]) { + let _ = net .sender .send(ServerToWorkerMessage::EnableEncryption(key)); } -pub fn handle_player_remove(state: &mut State, player: Entity) { - let comp = &state.network_components[player]; - let _ = comp.sender.send(ServerToWorkerMessage::Disconnect); -} - /// Broadcasts to all clients that the specified player /// has joined the game. This should be called /// whenever a player joins. From 7528df802890dc00f36484e2fafebe8439dc8403 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 22 Jul 2019 14:31:50 -0600 Subject: [PATCH 06/30] Start reimplementing block update logic --- core/src/world/mod.rs | 12 ++++++++++ server/src/entity.rs | 19 +++++++++++++++- server/src/main.rs | 1 + server/src/worldupdate.rs | 47 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 server/src/worldupdate.rs diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 3df5a49ab..6623e2177 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -91,6 +91,10 @@ impl ChunkMap { /// If the chunk in which the position resides /// does not exist, `Err` is returned. In all /// other cases, `Ok` is returned. + /// + /// Note that on the server side, calling this function + /// does not broadcast the update in any way. As such, + /// the according function should be called instead. pub fn set_block_at(&mut self, pos: BlockPosition, block: Block) -> Result<(), ()> { let chunk_pos = pos.chunk_pos(); @@ -109,6 +113,14 @@ impl ChunkMap { } } +impl Default for ChunkMap { + pub fn default() -> Self { + Self { + chunk_map: HashMap::new(), + } + } +} + fn chunk_relative_pos(block_pos: BlockPosition) -> (usize, usize, usize) { ( (block_pos.x % 16) as usize, diff --git a/server/src/entity.rs b/server/src/entity.rs index f9106a8bb..76a5fe994 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -2,10 +2,11 @@ //! and `PlayerComponent`. In the future, will also //! provide entity-specific components and systems. +use crate::disconnect_player; use feather_core::world::Position; use feather_core::Gamemode; use specs::storage::BTreeStorage; -use specs::{Component, VecStorage}; +use specs::{Component, Entity, LazyUpdate, ReadStorage, VecStorage}; use uuid::Uuid; pub struct PlayerComponent { @@ -27,3 +28,19 @@ pub struct EntityComponent { impl Component for EntityComponent { type Storage = VecStorage; } + +/// Confirms that a player is past the login +/// sequence and has joined the server, disconnecting +/// them and returning `false` if they have not. +pub fn check_player_joined( + player: Entity, + pcomps: &ReadStorage, + lazy: &LazyUpdate, +) -> bool { + if !pcomps.contains(player) { + disconnect_player(player, "You have not yet joined!", lazy); + return false; + } + + true +} diff --git a/server/src/main.rs b/server/src/main.rs index 561654c80..67d2b7874 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,6 +18,7 @@ pub mod initialhandler; pub mod io; pub mod network; pub mod prelude; +pub mod worldupdate; use crate::entity::EntityComponent; use crate::initialhandler::InitialHandlerComponent; diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs new file mode 100644 index 000000000..6bc3e23df --- /dev/null +++ b/server/src/worldupdate.rs @@ -0,0 +1,47 @@ +//! This module handles packets relating to world updates, +//! including block update packets. + +use crate::entity::{check_player_joined, PlayerComponent}; +use crate::network::PacketQueue; +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::{PlayerDigging, PlayerDiggingStatus}; +use feather_core::network::packet::PacketType; +use feather_core::world::{BlockPosition, ChunkMap}; +use feather_core::Gamemode; +use specs::{LazyUpdate, Read, ReadStorage, System, Write}; + +/// System responsible for polling for PlayerDigging +/// packets and the like and handling them accordingly. +pub struct WorldUpdateSystem; + +impl<'a> System<'a> for WorldUpdateSystem { + type SystemData = ( + Write<'a, ChunkMap>, + ReadStorage<'a, PlayerComponent>, + Read<'a, PacketQueue>, + Read<'a, LazyUpdate>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (chunk_map, pcomps, packet_queue, lazy) = data; + + let mut packets = vec![]; + packets.push(packet_queue.for_packet(PacketType::PlayerDigging)); + + // Handle packets + for (player, _packet) in packets { + if !check_player_joined(player, &pcomps, &lazy) { + continue; + } + let pcomp = pcomps.get(player).unwrap(); + + let packet = cast_packet::(&_packet); + match packet.status { + PlayerDiggingStatus::StartedDigging => if pcomp.gamemode == Gamemode::Creative {}, + } + } + } +} + +/// Handles a broken block by a player. +fn handle_block_break(chunk_map: &mut ChunkMap, pos: BlockPosition) {} From 8e0367fa13540d9f8712b548506ded929d41b803 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 22 Jul 2019 14:53:15 -0600 Subject: [PATCH 07/30] Complete world update code --- .codecov.yml | 2 +- server/src/worldupdate.rs | 63 +++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index a02264cc0..3d915b19a 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,6 @@ codecov: notify: - require_ci_to_pass: no + require_ci_to_pass: yes ignore: - "blocks" \ No newline at end of file diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 6bc3e23df..9073698ef 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -1,14 +1,17 @@ //! This module handles packets relating to world updates, //! including block update packets. +use crate::disconnect_player; use crate::entity::{check_player_joined, PlayerComponent}; -use crate::network::PacketQueue; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::BlockChange; use feather_core::network::packet::implementation::{PlayerDigging, PlayerDiggingStatus}; use feather_core::network::packet::PacketType; +use feather_core::world::block::{Block, BlockToId}; use feather_core::world::{BlockPosition, ChunkMap}; use feather_core::Gamemode; -use specs::{LazyUpdate, Read, ReadStorage, System, Write}; +use specs::{Join, LazyUpdate, Read, ReadStorage, System, Write}; /// System responsible for polling for PlayerDigging /// packets and the like and handling them accordingly. @@ -18,12 +21,13 @@ impl<'a> System<'a> for WorldUpdateSystem { type SystemData = ( Write<'a, ChunkMap>, ReadStorage<'a, PlayerComponent>, + ReadStorage<'a, NetworkComponent>, Read<'a, PacketQueue>, Read<'a, LazyUpdate>, ); fn run(&mut self, data: Self::SystemData) { - let (chunk_map, pcomps, packet_queue, lazy) = data; + let (mut chunk_map, pcomps, netcomps, packet_queue, lazy) = data; let mut packets = vec![]; packets.push(packet_queue.for_packet(PacketType::PlayerDigging)); @@ -33,15 +37,64 @@ impl<'a> System<'a> for WorldUpdateSystem { if !check_player_joined(player, &pcomps, &lazy) { continue; } + let pcomp = pcomps.get(player).unwrap(); let packet = cast_packet::(&_packet); match packet.status { - PlayerDiggingStatus::StartedDigging => if pcomp.gamemode == Gamemode::Creative {}, + PlayerDiggingStatus::StartedDigging => { + if pcomp.gamemode == Gamemode::Creative { + if handle_block_break(&mut chunk_map, packet.location, &netcomps, &pcomps) + .is_err() + { + disconnect_player( + player, + "Attempted to break block in unloaded chunk", + &lazy, + ); + } + } + } + PlayerDiggingStatus::FinishedDigging => { + if handle_block_break(&mut chunk_map, packet.location, &netcomps, &pcomps) + .is_err() + { + disconnect_player( + player, + "Attempted to break block in unloaded chunk", + &lazy, + ); + } + } + status => warn!("Unhandled Player Digging status: {:?}", status), } } } } /// Handles a broken block by a player. -fn handle_block_break(chunk_map: &mut ChunkMap, pos: BlockPosition) {} +fn handle_block_break( + chunk_map: &mut ChunkMap, + pos: BlockPosition, + netcomps: &ReadStorage, + pcomps: &ReadStorage, +) -> Result<(), ()> { + chunk_map.set_block_at(pos, Block::Air)?; + + broadcast_block_update(pos, Block::Air, netcomps, pcomps); + + Ok(()) +} + +/// Broadcasts a block update to all joined players. +fn broadcast_block_update( + pos: BlockPosition, + new_block: Block, + netcomps: &ReadStorage, + pcomps: &ReadStorage, +) { + for (net, _) in (netcomps, pcomps).join() { + let block_update = BlockChange::new(pos, new_block.block_state_id()); + send_packet_to_player(net, block_update); + } +} From d96bda7efa80bcfb114399eb1ba18c9b07e8cbdb Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 10:02:26 -0600 Subject: [PATCH 08/30] Implement player movement broadcasting --- core/src/world/mod.rs | 2 +- server/src/entity.rs | 163 ++++++++++++++++++++++++- server/src/initialhandler.rs | 14 +-- server/src/io/mod.rs | 6 +- server/src/main.rs | 3 +- server/src/network.rs | 230 ++--------------------------------- server/src/worldupdate.rs | 2 +- 7 files changed, 185 insertions(+), 235 deletions(-) diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 6623e2177..1c67ddae3 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -114,7 +114,7 @@ impl ChunkMap { } impl Default for ChunkMap { - pub fn default() -> Self { + fn default() -> Self { Self { chunk_map: HashMap::new(), } diff --git a/server/src/entity.rs b/server/src/entity.rs index 76a5fe994..9ceeab605 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -3,10 +3,21 @@ //! provide entity-specific components and systems. use crate::disconnect_player; +use crate::network::{send_packet_boxed_to_player, NetworkComponent, PacketQueue}; +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::{ + EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, +}; +use feather_core::network::packet::implementation::{ + EntityLook, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, +}; +use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::Position; use feather_core::Gamemode; use specs::storage::BTreeStorage; -use specs::{Component, Entity, LazyUpdate, ReadStorage, VecStorage}; +use specs::{ + Component, Entities, Entity, LazyUpdate, Read, ReadStorage, System, VecStorage, WriteStorage, +}; use uuid::Uuid; pub struct PlayerComponent { @@ -29,6 +40,143 @@ impl Component for EntityComponent { type Storage = VecStorage; } +/// System for handling player update packets, +/// including movement and inventory packets. +pub struct PlayerUpdateSystem; + +impl<'a> System<'a> for PlayerUpdateSystem { + type SystemData = ( + WriteStorage<'a, EntityComponent>, + ReadStorage<'a, PlayerComponent>, + Read<'a, PacketQueue>, + ReadStorage<'a, NetworkComponent>, + Entities<'a>, + Read<'a, LazyUpdate>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut ecomps, pcomps, packet_queue, netcomps, entities, lazy) = data; + + // Take movement packets + let mut packets = vec![]; + packets.append(&mut packet_queue.for_packet(PacketType::PlayerPosition)); + packets.append(&mut packet_queue.for_packet(PacketType::PlayerPositionAndLookServerbound)); + packets.append(&mut packet_queue.for_packet(PacketType::PlayerLook)); + + // Handle movement packets + for (player, packet) in packets { + let ecomp = ecomps.get(player).unwrap(); + + let mut has_moved = false; + let mut has_looked = false; + + // Get position using packet and old position + let new_pos = match packet.ty() { + PacketType::PlayerPosition => { + has_moved = true; + let packet = cast_packet::(&packet); + + Position::new( + packet.x, + packet.feet_y, + packet.z, + ecomp.position.pitch, + ecomp.position.yaw, + ) + } + PacketType::PlayerLook => { + has_looked = true; + let packet = cast_packet::(&packet); + + Position::new( + ecomp.position.x, + ecomp.position.y, + ecomp.position.z, + packet.pitch, + packet.yaw, + ) + } + PacketType::PlayerPositionAndLookServerbound => { + has_moved = true; + has_looked = true; + let packet = cast_packet::(&packet); + + Position::new(packet.x, packet.feet_y, packet.z, packet.pitch, packet.yaw) + } + _ => unreachable!(), + }; + + // Broadcast position update + broadcast_entity_movement( + player, + ecomp.position, + new_pos, + has_moved, + has_looked, + &netcomps, + &pcomps, + &entities, + ); + + // Set new position + ecomps.get_mut(player).unwrap().position = new_pos; + } + } +} + +/// Broadcasts to all joined players that an entity has moved. +fn broadcast_entity_movement( + entity: Entity, + old_pos: Position, + new_pos: Position, + has_moved: bool, + has_looked: bool, + netcomps: &ReadStorage, + pcomps: &ReadStorage, + entities: &Entities, +) { + assert!(has_moved || has_looked); + + let packet: Box = { + if has_moved { + let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); + + if has_looked { + Box::new(EntityLookAndRelativeMove::new( + entity.id() as i32, + rx, + ry, + rz, + degrees_to_stops(new_pos.yaw), + degrees_to_stops(new_pos.pitch), + true, + )) + } else { + Box::new(EntityRelativeMove::new( + entity.id() as i32, + rx, + ry, + rz, + true, + )) + } + } else { + Box::new(EntityLook::new( + entity.id() as i32, + degrees_to_stops(new_pos.yaw), + degrees_to_stops(new_pos.pitch), + true, + )); + } + }; + + for (net, _, e) in (netcomps, pcomps, entities).join() { + if entity != e { + send_packet_boxed_to_player(net, packet); + } + } +} + /// Confirms that a player is past the login /// sequence and has joined the server, disconnecting /// them and returning `false` if they have not. @@ -44,3 +192,16 @@ pub fn check_player_joined( true } + +/// Calculates the relative move fields +/// as used in the Entity Relative Move packets. +pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { + let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; + let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; + let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; + (x, y, z) +} + +pub fn degrees_to_stops(degs: f32) -> u8 { + ((degs / 360.0) * 256.) as u8 +} diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 48c752e32..bc6457cbe 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -67,12 +67,12 @@ impl InitialHandlerComponent { /// System for handling the login sequence. pub struct InitialHandlerSystem; -impl<'a> System for InitialHandlerSystem { +impl<'a> System<'a> for InitialHandlerSystem { type SystemData = ( Entities<'a>, WriteStorage<'a, IntitialHandlerComponent>, ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, PacketQueue>, + Read<'a, PacketQueue>, Read<'a, Config>, Write<'a, PlayerCount>, // LazyUpdate is used to add player + entity @@ -87,11 +87,11 @@ impl<'a> System for InitialHandlerSystem { data; let mut packets = vec![]; - packets.append(packet_queue.for_packet(PacketType::Handshake)); - packets.append(packet_queue.for_packet(PacketType::LoginStart)); - packets.append(packet_queue.for_packet(PacketType::Request)); - packets.append(packet_queue.for_packet(PacketType::Ping)); - packets.append(packet_queue.for_packet(PacketType::EncryptionResponse)); + packets.append(&mut packet_queue.for_packet(PacketType::Handshake)); + packets.append(&mut packet_queue.for_packet(PacketType::LoginStart)); + packets.append(&mut packet_queue.for_packet(PacketType::Request)); + packets.append(&mut packet_queue.for_packet(PacketType::Ping)); + packets.append(&mut packet_queue.for_packet(PacketType::EncryptionResponse)); for (player, packet) in packets { if !player_check(player, &ih_comps, &lazy) { diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 5a343bcfc..78c4056f0 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -34,12 +34,12 @@ pub struct NewClientInfo { pub ip: SocketAddr, pub sender: Sender, - pub receiver: Receiver, + pub receiver: crossbeam::channel::Receiver, } pub struct NetworkIoManager { pub sender: Sender, - pub receiver: Receiver, + pub receiver: crossbeam::channel::Receiver, } impl NetworkIoManager { @@ -60,7 +60,7 @@ impl NetworkIoManager { workers.push((send1, recv2)); } - let (sender1, receiver1) = channel(); + let (sender1, receiver1) = crossbeam::channel::unbounded(); let (sender2, receiver2) = channel(); thread::spawn(move || listener::start(addr.to_string(), sender1, receiver2, workers)); diff --git a/server/src/main.rs b/server/src/main.rs index 67d2b7874..ac98c7206 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -30,7 +30,6 @@ use specs::{ Component, DenseVecStorage, Dispatcher, DispatcherBuilder, Entity, LazyUpdate, VecStorage, World, WorldExt, }; -use std::thread::sleep; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub const TPS: u64 = 20; @@ -102,7 +101,7 @@ fn init_io_manager(config: &Config) -> io::NetworkIoManager { } /// Initializes the Specs world. -fn init_world(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher) { +fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher<'a, 'b>) { let mut world = World::new(); world.insert(config); world.insert(ioman); diff --git a/server/src/network.rs b/server/src/network.rs index 6bbdedd9d..4660f871c 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -7,7 +7,10 @@ use feather_blocks::Block; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; -use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, Write, WriteStorage}; +use specs::{ + Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, + Write, WriteStorage, +}; use std::sync::Mutex; //const MAX_KEEP_ALIVE_TIME: u64 = 30; @@ -75,7 +78,7 @@ impl Default for PacketQueue { pub struct NetworkComponent { sender: Sender, - receiver: Receiver, + receiver: crossbeam::channel::Receiver, /// A vector of all chunks that are currently /// being loaded and should be sent to the player /// once they have been loaded. @@ -86,7 +89,7 @@ pub struct NetworkComponent { impl NetworkComponent { pub fn new( sender: Sender, - receiver: Receiver, + receiver: crossbeam::channel::Receiver, ) -> Self { Self { sender, @@ -96,6 +99,10 @@ impl NetworkComponent { } } +impl Component for NetworkComponent { + type Storage = DenseVecStorage; +} + /// The network system, responsible for /// receiving and buffering packets received /// from players. Received packets @@ -170,82 +177,6 @@ impl<'a> System<'a> for NetworkSystem { } } -// TODO proper validation of new position -fn handle_player_pos_and_look( - state: &mut State, - player: Entity, - packet: &PlayerPositionAndLookServerbound, -) { - let ecomp = &state.entity_components[player]; - let old_pos = ecomp.position; - - let new_pos = Position::new(packet.x, packet.feet_y, packet.z, packet.pitch, packet.yaw); - - broadcast_entity_movement(state, player, old_pos, new_pos, true, true); - - state.entity_components[player].position = new_pos; -} - -fn handle_player_pos(state: &mut State, player: Entity, packet: &PlayerPosition) { - let ecomp = &state.entity_components[player]; - let old_pos = ecomp.position; - - let new_pos = Position::new( - packet.x, - packet.feet_y, - packet.z, - old_pos.pitch, - old_pos.yaw, - ); - - broadcast_entity_movement(state, player, old_pos, new_pos, true, false); - - state.entity_components[player].position = new_pos; -} - -fn handle_player_look(state: &mut State, player: Entity, packet: &PlayerLook) { - let ecomp = &state.entity_components[player]; - let old_pos = ecomp.position; - - let new_pos = Position::new(old_pos.x, old_pos.y, old_pos.z, packet.pitch, packet.yaw); - - broadcast_entity_movement(state, player, old_pos, new_pos, false, true); - - state.entity_components[player].position = new_pos; -} - -fn handle_player_digging(state: &mut State, player: Entity, packet: &PlayerDigging) { - match packet.status { - PlayerDiggingStatus::FinishedDigging => { - if state - .chunk_map - .set_block_at(packet.location, Block::Air) - .is_err() - { - // TODO kick player - } - broadcast_block_update(state, packet.location); - } - PlayerDiggingStatus::StartedDigging => { - let pcomp = &state.player_components[player]; - if pcomp.gamemode == Gamemode::Creative { - // Break block instantly - TODO not with sword in hand - if state - .chunk_map - .set_block_at(packet.location, Block::Air) - .is_err() - { - // TODO kick player - warn!("Client sent invalid Player Digging packet"); - return; - } - broadcast_block_update(state, packet.location); - } - } - _ => (), // TODO - } -} - pub fn enable_compression_for_player(net: &NetworkComponent, threshold: usize) { let _ = net .sender @@ -258,29 +189,6 @@ pub fn enable_encryption_for_player(net: &NetworkComponent, key: [u8; 16]) { .send(ServerToWorkerMessage::EnableEncryption(key)); } -/// Broadcasts to all clients that the specified player -/// has joined the game. This should be called -/// whenever a player joins. -/// -/// This function is currently called by the initial handler. -pub fn broadcast_player_join( - player: Entity, - netcomps: &ReadStorage, - pcomps: &ReadStorage, - ecomps: &ReadStorage, -) { - let ecomp = ecomps.get(player).unwrap(); - let pcomp = pcomps.get(player).unwrap(); - let (player_info, spawn_player) = get_player_initialization_packets(ecomp, pcomp, player); - - for (net, _) in (netcomps, pcomps).join() { - send_packet_to_player(net, player_info.clone()); - if *p != player { - send_packet_to_player(net, spawn_player.clone()); - } - } -} - /// Returns the player info and spawn player packets /// for the given player. pub fn get_player_initialization_packets( @@ -344,115 +252,6 @@ pub fn get_player_initialization_packets( (player_info, spawn_player) } -/// Broadcasts to all joined players that the -/// given player has left the server. This should -/// remove the player from the tablist. -pub fn broadcast_player_leave(state: &mut State, player: Entity) { - let ecomp = &state.entity_components[player]; - - let player_info = PlayerInfo::new(PlayerInfoAction::RemovePlayer, ecomp.uuid.clone()); - send_packet_to_all_players(state, player_info, Some(player)); - - let destroy_entities = DestroyEntities::new(vec![player.index() as i32]); - send_packet_to_all_players(state, destroy_entities, Some(player)); -} - -/// Notifies all players within range -/// that an entity has moved. This -/// entity can be a player. -/// -/// The `has_moved` and `has_moved` fields indicate -/// whether the entity has moved its position -/// or changed its pitch/yaw. These values -/// are used to determine which packet to send: -/// for example, if an entity has only moved and not looked, -/// an Entity Relative Move packet is sent -/// rather than an Entity Look and Relative Move. -/// -/// In the event that the entity has moved -/// more than 8 blocks, an Entity Teleport packet -/// is sent instead. -pub fn broadcast_entity_movement( - state: &mut State, - entity: Entity, - old_pos: Position, - new_pos: Position, - has_moved: bool, - has_looked: bool, -) { - let ecomp = &state.entity_components[entity]; - - let dist = new_pos.distance(old_pos).abs(); - - if dist <= 8.0 { - if has_moved && has_looked { - // Entity Look and Relative Move - let (dx, dy, dz) = calculate_relative_move(old_pos, new_pos); - let packet = EntityLookAndRelativeMove::new( - entity.index() as i32, - dx, - dy, - dz, - degrees_to_stops(new_pos.yaw), - degrees_to_stops(new_pos.pitch), - ecomp.on_ground, - ); - send_packet_to_all_players(state, packet, Some(entity)); - } else if has_moved { - // Entity Relative Move - let (dx, dy, dz) = calculate_relative_move(old_pos, new_pos); - let packet = - EntityRelativeMove::new(entity.index() as i32, dx, dy, dz, ecomp.on_ground); - send_packet_to_all_players(state, packet, Some(entity)); - } else if has_looked { - // Entity Look - let packet = EntityLook::new( - entity.index() as i32, - degrees_to_stops(new_pos.yaw), - degrees_to_stops(new_pos.pitch), - ecomp.on_ground, - ); - send_packet_to_all_players(state, packet, Some(entity)); - } - } else { - // TODO - } - - // Send Entity Head Look for head yaw - if has_looked { - let packet = EntityHeadLook::new(entity.index() as i32, degrees_to_stops(new_pos.yaw)); - send_packet_to_all_players(state, packet, Some(entity)); - } -} - -pub fn broadcast_block_update(state: &mut State, pos: BlockPosition) { - // TODO only send for players in range - let block = state.chunk_map.block_at(pos).unwrap(); - let packet = BlockChange::new(pos, block.block_state_id() as i32); - - send_packet_to_all_players(state, packet, None); -} - -/// Sends a packet to all (joined) players on the server, excluding -/// `neq`, if it exists. -fn send_packet_to_all_players( - net_comps: &ReadStorage, - player_comps: &ReadStorage, - entities: &Entities, - packet: P, - neq: Option, -) { - for (entity, net, player) in (entities, net_comps, player_comps).join() { - if let Some(e) = neq.as_ref() { - if e == entity { - continue; // Exclude this entity - } - } - - send_packet_to_player(net, packet.clone()); - } -} - /// Sends a packet to the given player. pub fn send_packet_to_player(comp: &NetworkComponent, packet: P) { let _ = comp @@ -468,12 +267,3 @@ pub fn send_packet_boxed_to_player(comp: &NetworkComponent, packet: Box) pub fn degrees_to_stops(degs: f32) -> u8 { ((degs / 360.0) * 256.) as u8 } - -/// Calculates the relative move fields -/// as used in the Entity Relative Move packets. -pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { - let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; - let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; - let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; - (x, y, z) -} diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 9073698ef..bf887ea36 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -30,7 +30,7 @@ impl<'a> System<'a> for WorldUpdateSystem { let (mut chunk_map, pcomps, netcomps, packet_queue, lazy) = data; let mut packets = vec![]; - packets.push(packet_queue.for_packet(PacketType::PlayerDigging)); + packets.append(&mut packet_queue.for_packet(PacketType::PlayerDigging)); // Handle packets for (player, _packet) in packets { From d124259645ce5a461e0b63dba462e264c1b02419 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 10:57:17 -0600 Subject: [PATCH 09/30] Fix a ton of compiler errors with Specs refactor --- Cargo.lock | 7 +-- core/src/world/mod.rs | 2 +- server/Cargo.toml | 2 +- server/src/chunkclient.rs | 7 +++ server/src/entity.rs | 11 ++--- server/src/initialhandler.rs | 94 +++++++++++++++++++++++++----------- server/src/io/mod.rs | 12 +++-- server/src/main.rs | 17 ++++--- server/src/network.rs | 13 ++--- server/src/worldupdate.rs | 2 +- 10 files changed, 109 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90d58b51c..12949db17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,7 +454,7 @@ dependencies = [ "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-extras 2.0.5 (git+https://github.com/caelunshun/mio-extras?rev=e5dca5d)", "mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "mojang-api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -862,8 +862,9 @@ dependencies = [ [[package]] name = "mio-extras" version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/caelunshun/mio-extras?rev=e5dca5d#e5dca5d385b6e111810330a315b1fb2ae9bff244" dependencies = [ + "crossbeam 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2076,7 +2077,7 @@ dependencies = [ "checksum miniz_oxide 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b6c3756d66cf286314d5f7ebe74886188a9a92f5eee68b06f31ac2b4f314c99d" "checksum miniz_oxide_c_api 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5b78ca5446dd9fe0dab00e058731b6b08a8c1d2b9cdb8efb10876e24e9ae2494" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" -"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" +"checksum mio-extras 2.0.5 (git+https://github.com/caelunshun/mio-extras?rev=e5dca5d)" = "" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8bf36d14570661eebd299640fff1fae157c6d7da34bcbf4e4eba1c3b47b46ec9" "checksum mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a53c3de46bf8e9cbe2b80e5086bbc8659bdf508c3719d50c2f366c9368d5726c" diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 1c67ddae3..031c66ffe 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -65,7 +65,7 @@ impl ChunkMap { /// Retrieves the chunk at the specified location. /// If the chunk is not loaded, `None` will be returned. - pub fn chunk_at(&mut self, pos: ChunkPosition) -> Option<&Chunk> { + pub fn chunk_at(&self, pos: ChunkPosition) -> Option<&Chunk> { if let Some(chunk) = self.chunk_map.get(&pos) { return Some(chunk); } diff --git a/server/Cargo.toml b/server/Cargo.toml index 06961083b..174c7f8b9 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" feather_core = { path = "../core" } feather_blocks = { path = "../blocks/lib" } mio = "0.6.19" -mio-extras = "2.0.5" +mio-extras = { git = "https://github.com/caelunshun/mio-extras", rev = "e5dca5d" } crossbeam = "0.7.1" log = "0.4.6" simple_logger = "1.3.0" diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index 6de479e7b..df3454009 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -10,6 +10,13 @@ pub struct ChunkWorkerHandle { receiver: Receiver, } +impl Default for ChunkWorkerHandle { + fn default() -> Self { + let (sender, receiver) = chunkworker::start("world"); + Self { sender, receiver } + } +} + /// System for receiving loaded chunks from the chunk worker thread. pub struct ChunkSystem; diff --git a/server/src/entity.rs b/server/src/entity.rs index 9ceeab605..f272d9c46 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -6,17 +6,16 @@ use crate::disconnect_player; use crate::network::{send_packet_boxed_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ - EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, -}; -use feather_core::network::packet::implementation::{ - EntityLook, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, + EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, PlayerLook, PlayerPosition, + PlayerPositionAndLookServerbound, }; use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::Position; use feather_core::Gamemode; use specs::storage::BTreeStorage; use specs::{ - Component, Entities, Entity, LazyUpdate, Read, ReadStorage, System, VecStorage, WriteStorage, + Component, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, VecStorage, + WriteStorage, }; use uuid::Uuid; @@ -166,7 +165,7 @@ fn broadcast_entity_movement( degrees_to_stops(new_pos.yaw), degrees_to_stops(new_pos.pitch), true, - )); + )) } }; diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index bc6457cbe..0a0f51ade 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -1,7 +1,7 @@ use crate::entity::{EntityComponent, PlayerComponent}; use crate::network::{ - broadcast_player_join, enable_compression_for_player, enable_encryption_for_player, - get_player_initialization_packets, send_packet_to_player, NetworkComponent, PacketQueue, + enable_compression_for_player, enable_encryption_for_player, get_player_initialization_packets, + send_packet_to_player, NetworkComponent, PacketQueue, }; use crate::prelude::*; use feather_core::network::packet::{implementation::*, Packet, PacketType}; @@ -70,8 +70,10 @@ pub struct InitialHandlerSystem; impl<'a> System<'a> for InitialHandlerSystem { type SystemData = ( Entities<'a>, - WriteStorage<'a, IntitialHandlerComponent>, + WriteStorage<'a, InitialHandlerComponent>, ReadStorage<'a, NetworkComponent>, + ReadStorage<'a, PlayerComponent>, + ReadStorage<'a, EntityComponent>, Read<'a, PacketQueue>, Read<'a, Config>, Write<'a, PlayerCount>, @@ -80,11 +82,24 @@ impl<'a> System<'a> for InitialHandlerSystem { // other systems from being blocked by the initial // handler because of a dependency on WriteStorage. Read<'a, LazyUpdate>, + Read<'a, ChunkMap>, + Read<'a, ChunkWorkerHandle>, ); fn run(&mut self, data: Self::SystemData) { - let (entities, mut ih_comps, net_comps, packet_queue, config, mut player_count, lazy) = - data; + let ( + entities, + mut ih_comps, + net_comps, + pcomps, + ecomps, + packet_queue, + config, + mut player_count, + lazy, + chunk_map, + chunk_handle, + ) = data; let mut packets = vec![]; packets.append(&mut packet_queue.for_packet(PacketType::Handshake)); @@ -104,9 +119,13 @@ impl<'a> System<'a> for InitialHandlerSystem { &entities, &mut ih_comps, &net_comps, + &pcomps, + &ecomps, &config, &mut player_count, &lazy, + &chunk_map, + &chunk_handle, ) { disconnect_player( player, @@ -172,9 +191,13 @@ fn handle_packet( entities: &Entities, ihcomps: &mut WriteStorage, netcomps: &ReadStorage, + pcomps: &ReadStorage, + ecomps: &ReadStorage, config: &Config, player_count: &mut Write, lazy: &LazyUpdate, + chunk_map: &ChunkMap, + chunk_handle: &ChunkWorkerHandle, ) -> Result<(), Error> { let ihcomp = ihcomps.get_mut(player).unwrap(); let netcomp = netcomps.get(player).unwrap(); @@ -201,9 +224,10 @@ fn handle_packet( netcomps, chunk_handle, chunk_map, + entities, lazy, cast_packet::(&packet), - ), + )?, PacketType::EncryptionResponse => handle_encryption_response( player, config, @@ -212,10 +236,11 @@ fn handle_packet( pcomps, netcomps, chunk_handle, + entities, lazy, chunk_map, cast_packet::(&packet), - ), + )?, _ => panic!("Invalid packet"), } @@ -260,7 +285,7 @@ fn handle_request( }, "players": { "max": config.server.max_players, - "online": joined_players.len(), + "online": player_count.0, }, "description": config.server.motd, }); @@ -288,7 +313,7 @@ fn handle_ping( let pong = Pong::new(packet.payload); send_packet_to_player(net, pong); - lazy.remove(player); + lazy.exec_mut(|world| world.delete_entity(player).unwrap()); debug!("Status handling success"); Ok(()) @@ -304,6 +329,7 @@ fn handle_login_start<'a>( netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, chunk_map: &ChunkMap, + entities: &Entities, lazy: &LazyUpdate, packet: &LoginStart, ) -> Result<(), Error> { @@ -321,7 +347,7 @@ fn handle_login_start<'a>( let key_bytes = rsa_key.public_key_to_der().unwrap(); let mut verify_token = vec![]; - verify_token.extend_from_slice(&ih.verify_token); + verify_token.extend_from_slice(&comp.verify_token); let encryption_request = EncryptionRequest::new( "".to_string(), // Server ID - always empty nowadays @@ -347,7 +373,7 @@ fn handle_login_start<'a>( let entity_comp = EntityComponent { position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), uuid: Uuid::new_v4(), - display_name: ih.username.as_ref().unwrap().clone(), + display_name: comp.username.as_ref().unwrap().clone(), on_ground: true, }; lazy.insert(player, entity_comp); @@ -359,6 +385,7 @@ fn handle_login_start<'a>( pcomps, netcomps, chunk_handle, + entities, lazy, chunk_map, config, @@ -376,6 +403,7 @@ fn handle_encryption_response( pcomps: &ReadStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, + entities: &Entities, lazy: &LazyUpdate, chunk_map: &ChunkMap, packet: &EncryptionResponse, @@ -385,6 +413,8 @@ fn handle_encryption_response( return Err(Error::InvalidPacket(PacketType::Ping)); } + let net = netcomps.get(player).unwrap(); + let rsa = comp.rsa_key.as_ref().unwrap(); let secret: [u8; 16] = { @@ -433,7 +463,7 @@ fn handle_encryption_response( } }; - if !compare_verify_tokens(ih.verify_token.clone(), verify_token) { + if !compare_verify_tokens(comp.verify_token.clone(), verify_token) { return Err(Error::MalformedData( "Verify tokens do not match".to_string(), )); @@ -455,7 +485,7 @@ fn handle_encryption_response( let entity_comp = EntityComponent { position: Position::new(0.0, 64.0, 0.0, 0.0, 0.0), uuid: Uuid::from_str(&res.id).unwrap(), - display_name: ih.username.as_ref().unwrap().clone(), + display_name: comp.username.as_ref().unwrap().clone(), on_ground: true, }; lazy.insert(player, entity_comp); @@ -464,7 +494,7 @@ fn handle_encryption_response( return Err(Error::AuthenticationFailed); } - enable_encryption_for_player(state, player, secret); + enable_encryption_for_player(net, secret); finish( player, @@ -473,6 +503,7 @@ fn handle_encryption_response( pcomps, netcomps, chunk_handle, + entities, lazy, chunk_map, config, @@ -488,25 +519,26 @@ fn finish( pcomps: &ReadStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, + entities: &Entities, lazy: &LazyUpdate, chunk_map: &ChunkMap, config: &Config, ) { - let net = ncomps.get(player).unwrap(); + let net = netcomps.get(player).unwrap(); // Enable compression if needed let threshold = config.io.compression_threshold; if threshold > 0 { let set_compression = SetCompression::new(threshold); send_packet_to_player(net, set_compression); - enable_compression_for_player(state, player, threshold as usize); + enable_compression_for_player(net, threshold as usize); } - let entity_comp = state.entity_components.get(player).unwrap(); + let ecomp = ecomps.get(player).unwrap(); let login_success = LoginSuccess::new( - entity_comp.uuid.to_hyphenated_ref().to_string(), - entity_comp.display_name.to_string(), + ecomp.uuid.to_hyphenated_ref().to_string(), + ecomp.display_name.to_string(), ); send_packet_to_player(net, login_success); @@ -517,8 +549,10 @@ fn finish( pcomps, netcomps, chunk_handle, + entities, lazy, chunk_map, + config, ); } @@ -529,12 +563,13 @@ fn join_game( pcomps: &ReadStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, + entities: &Entities, lazy: &LazyUpdate, chunk_map: &ChunkMap, + config: &Config, ) { let join_game = JoinGame::new( - player.index() as i32, - is: open, + player.id() as i32, Gamemode::Creative.get_id(), Dimension::Overwold.get_id(), Difficulty::Medium.get_id(), @@ -549,22 +584,23 @@ fn join_game( // Send chunk data. If a chunk in the view distance // hasn't been loaded, a load request will be queued. - let view_distance = state.config.server.view_distance as i32; + let view_distance = config.server.view_distance as i32; for x in -view_distance..view_distance + 1 { for y in -view_distance..view_distance + 1 { let pos = ChunkPosition::new(x, y); - if let Some(chunk) = state.chunk_map.chunk_at(pos) { + if let Some(chunk) = chunk_map.chunk_at(pos) { let chunk_data = ChunkData::new(chunk.clone()); send_packet_to_player(net, chunk_data); } else { // Queue chunk for loading. - load_chunk(handle, pos); + load_chunk(chunk_handle, pos); // Make sure that the chunk is sent once loaded lazy.exec_mut(|world| { world .write_component::() .get_mut(player) + .unwrap() .chunks_to_send .push(pos); }); @@ -573,7 +609,7 @@ fn join_game( } if net.chunks_to_send.is_empty() { - complete_join_game(player, ihcomps, ecomps, pcomps, netcomps); + complete_join_game(player, ihcomps, ecomps, pcomps, netcomps, entities); } else { // If not all chunks have been sent, we need to wait // until they're loaded and sent before spawning the player. @@ -589,9 +625,11 @@ pub fn complete_join_game( ecomps: &ReadStorage, pcomps: &ReadStorage, netcomps: &ReadStorage, + entities: &Entities, ) { // Send spawn position + player position // TODO proper persistence + let net = netcomps.get(player).unwrap(); let spawn_position = SpawnPosition::new(BlockPosition::new(0, 64, 0)); send_packet_to_player(net, spawn_position); @@ -600,16 +638,16 @@ pub fn complete_join_game( send_packet_to_player(net, pos_and_look); // Send other players on the server - for (ecomp, pcomp, net) in (ecomps, pcomps, netcomps).join() { + for (ecomp, pcomp, net, other_player) in (ecomps, pcomps, netcomps, entities).join() { let (player_info, spawn_player) = - get_player_initialization_packets(ecomp, pcomp, *other_player); + get_player_initialization_packets(ecomp, pcomp, other_player); send_packet_to_player(net, player_info); send_packet_to_player(net, spawn_player); } ihcomps.remove(player); - broadcast_player_join(player, netcomps, pcomps, ecomps); + //broadcast_player_join(player, netcomps, pcomps, ecomps); info!("A player joined the game"); } diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 78c4056f0..5803f08d4 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -34,12 +34,12 @@ pub struct NewClientInfo { pub ip: SocketAddr, pub sender: Sender, - pub receiver: crossbeam::channel::Receiver, + pub receiver: Receiver, } pub struct NetworkIoManager { pub sender: Sender, - pub receiver: crossbeam::channel::Receiver, + pub receiver: Receiver, } impl NetworkIoManager { @@ -60,7 +60,7 @@ impl NetworkIoManager { workers.push((send1, recv2)); } - let (sender1, receiver1) = crossbeam::channel::unbounded(); + let (sender1, receiver1) = channel(); let (sender2, receiver2) = channel(); thread::spawn(move || listener::start(addr.to_string(), sender1, receiver2, workers)); @@ -78,3 +78,9 @@ impl NetworkIoManager { info!("Shut down IO event loop"); } } + +impl Default for NetworkIoManager { + fn default() -> Self { + Self::start("127.0.0.1".parse().unwrap(), 8) + } +} diff --git a/server/src/main.rs b/server/src/main.rs index ac98c7206..4918c16df 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -23,7 +23,7 @@ pub mod worldupdate; use crate::entity::EntityComponent; use crate::initialhandler::InitialHandlerComponent; use crate::network::{send_packet_to_player, NetworkComponent}; -use feather_core::network::packet::PacketType::DisconnectPlay; +use feather_core::network::packet::implementation::DisconnectPlay; use multimap::MultiMap; use prelude::*; use specs::{ @@ -73,7 +73,8 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { world.maintain(); // Increment tick count - *world.write_resource::().0 += 1; + let old_tick_count = world.read_resource::(); + world.insert(TickCount(old_tick_count.0 + 1)); // Sleep correct amount let end_time = current_time_in_millis(); @@ -95,8 +96,10 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { /// Starts the IO threads. fn init_io_manager(config: &Config) -> io::NetworkIoManager { - let ioman = - io::NetworkIoManager::start(config.server.addresss.into(), config.io.io_worker_threads); + let ioman = io::NetworkIoManager::start( + format!("127.0.0.1:{}", config.server.port).parse().unwrap(), + config.io.io_worker_threads, + ); ioman } @@ -165,12 +168,12 @@ pub fn current_time_in_millis() -> u64 { /// Disconnects the given player, removing them from the world. /// This operation is performed lazily. -pub fn disconnect_player(player: Entity, reason: &str, lazy: &LazyUpdate) { +pub fn disconnect_player(player: Entity, reason: &'static str, lazy: &LazyUpdate) { lazy.exec_mut(|world| { let packet = DisconnectPlay::new(reason.to_string()); - send_packet_to_player(world.read_component().get(player), packet); + send_packet_to_player(world.read_component().get(player).unwrap(), packet); - network::broadcast_player_leave(world.read_component(), player); + //network::broadcast_player_leave(world.read_component(), player); if let Some(ecomp) = world.read_component::().get(player) { info!("Disconnected player {}: {}", ecomp.display_name, reason); diff --git a/server/src/network.rs b/server/src/network.rs index 4660f871c..5e49030a5 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -64,7 +64,7 @@ impl PacketQueue { self.expand(&mut queue, ordinal); } - self.queue[ordinal].push((player, packet)); + queue[ordinal].push((player, packet)); } } @@ -78,7 +78,7 @@ impl Default for PacketQueue { pub struct NetworkComponent { sender: Sender, - receiver: crossbeam::channel::Receiver, + receiver: Receiver, /// A vector of all chunks that are currently /// being loaded and should be sent to the player /// once they have been loaded. @@ -89,7 +89,7 @@ pub struct NetworkComponent { impl NetworkComponent { pub fn new( sender: Sender, - receiver: crossbeam::channel::Receiver, + receiver: Receiver, ) -> Self { Self { sender, @@ -156,10 +156,7 @@ impl<'a> System<'a> for NetworkSystem { // TODO broadcast disconnect entities.delete(player); } - msg => panic!( - "Network system received invalid message from IO worker: {:?}", - msg - ), + msg => panic!("Network system received invalid message from IO worker}"), } } } @@ -239,7 +236,7 @@ pub fn get_player_initialization_packets( ]); let spawn_player = SpawnPlayer::new( - player.index() as i32, + player.id() as i32, ecomp.uuid.clone(), ecomp.position.x, ecomp.position.y, diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index bf887ea36..76aa650eb 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -94,7 +94,7 @@ fn broadcast_block_update( pcomps: &ReadStorage, ) { for (net, _) in (netcomps, pcomps).join() { - let block_update = BlockChange::new(pos, new_block.block_state_id()); + let block_update = BlockChange::new(pos, new_block.block_state_id() as i32); send_packet_to_player(net, block_update); } } From 2567e31a913bb57d05d285da57cbdc3d39ef865b Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 12:49:50 -0600 Subject: [PATCH 10/30] Fix compiler errors --- server/src/chunkclient.rs | 5 +-- server/src/entity.rs | 67 +++++++++++++++++++----------------- server/src/initialhandler.rs | 10 ++---- server/src/main.rs | 10 +++--- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index df3454009..bb064e2d1 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -90,10 +90,11 @@ mod tests { world.insert(chunk_map); world.insert(handle); - system.run_now(&world.res); + system.run_now(&world); // Confirm that chunk was loaded - let chunk = world.read_resource::().chunk_at(pos); + let chunk_map = world.read_resource::(); + let chunk = chunk_map.chunk_at(pos); assert!(chunk.is_some()); assert!(chunk.unwrap().position() == pos); diff --git a/server/src/entity.rs b/server/src/entity.rs index f272d9c46..0e413b146 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -3,7 +3,9 @@ //! provide entity-specific components and systems. use crate::disconnect_player; -use crate::network::{send_packet_boxed_to_player, NetworkComponent, PacketQueue}; +use crate::network::{ + send_packet_boxed_to_player, send_packet_to_player, NetworkComponent, PacketQueue, +}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, PlayerLook, PlayerPosition, @@ -136,42 +138,43 @@ fn broadcast_entity_movement( ) { assert!(has_moved || has_looked); - let packet: Box = { - if has_moved { - let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); - - if has_looked { - Box::new(EntityLookAndRelativeMove::new( - entity.id() as i32, - rx, - ry, - rz, - degrees_to_stops(new_pos.yaw), - degrees_to_stops(new_pos.pitch), - true, - )) - } else { - Box::new(EntityRelativeMove::new( - entity.id() as i32, - rx, - ry, - rz, - true, - )) - } - } else { - Box::new(EntityLook::new( + if has_moved { + let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); + + if has_looked { + let packet = EntityLookAndRelativeMove::new( entity.id() as i32, + rx, + ry, + rz, degrees_to_stops(new_pos.yaw), degrees_to_stops(new_pos.pitch), true, - )) + ); + for (net, _, e) in (netcomps, pcomps, entities).join() { + if entity != e { + send_packet_to_player(net, packet.clone()); + } + } + } else { + let packet = EntityRelativeMove::new(entity.id() as i32, rx, ry, rz, true); + for (net, _, e) in (netcomps, pcomps, entities).join() { + if entity != e { + send_packet_to_player(net, packet.clone()); + } + } } - }; - - for (net, _, e) in (netcomps, pcomps, entities).join() { - if entity != e { - send_packet_boxed_to_player(net, packet); + } else { + let packet = EntityLook::new( + entity.id() as i32, + degrees_to_stops(new_pos.yaw), + degrees_to_stops(new_pos.pitch), + true, + ); + for (net, _, e) in (netcomps, pcomps, entities).join() { + if entity != e { + send_packet_to_player(net, packet.clone()); + } } } } diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 0a0f51ade..ad170ac7a 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -127,11 +127,7 @@ impl<'a> System<'a> for InitialHandlerSystem { &chunk_map, &chunk_handle, ) { - disconnect_player( - player, - &format!("InitialHandler: error occurred: {}", e), - &lazy, - ); + disconnect_player(player, "InitialHandler: error occurred", &lazy); info!("InitialHandler: player was kicked: {}", e); } } @@ -313,7 +309,7 @@ fn handle_ping( let pong = Pong::new(packet.payload); send_packet_to_player(net, pong); - lazy.exec_mut(|world| world.delete_entity(player).unwrap()); + lazy.exec_mut(move |world| world.delete_entity(player).unwrap()); debug!("Status handling success"); Ok(()) @@ -596,7 +592,7 @@ fn join_game( // Queue chunk for loading. load_chunk(chunk_handle, pos); // Make sure that the chunk is sent once loaded - lazy.exec_mut(|world| { + lazy.exec_mut(move |world| { world .write_component::() .get_mut(player) diff --git a/server/src/main.rs b/server/src/main.rs index 4918c16df..544a21610 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -37,9 +37,9 @@ pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; pub const TICK_TIME: u64 = 1000 / TPS; -#[derive(Default)] +#[derive(Default, Debug, Clone, Copy)] pub struct PlayerCount(u32); -#[derive(Default)] +#[derive(Default, Debug, Clone, Copy)] pub struct TickCount(u64); fn main() { @@ -73,8 +73,8 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { world.maintain(); // Increment tick count - let old_tick_count = world.read_resource::(); - world.insert(TickCount(old_tick_count.0 + 1)); + let mut tick_count = world.write_resource::(); + tick_count.0 += 1; // Sleep correct amount let end_time = current_time_in_millis(); @@ -169,7 +169,7 @@ pub fn current_time_in_millis() -> u64 { /// Disconnects the given player, removing them from the world. /// This operation is performed lazily. pub fn disconnect_player(player: Entity, reason: &'static str, lazy: &LazyUpdate) { - lazy.exec_mut(|world| { + lazy.exec_mut(move |world| { let packet = DisconnectPlay::new(reason.to_string()); send_packet_to_player(world.read_component().get(player).unwrap(), packet); From 9df104581a68d38bf29d5333c612ed68ea947478 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 12:55:20 -0600 Subject: [PATCH 11/30] Fix compiler warnings --- server/src/chunkclient.rs | 6 +++--- server/src/entity.rs | 8 +++----- server/src/initialhandler.rs | 2 +- server/src/main.rs | 12 ++++-------- server/src/network.rs | 13 ++++++------- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index bb064e2d1..b1df01c84 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -74,7 +74,7 @@ mod tests { #[test] fn test_chunk_system() { - let (send1, recv1) = crossbeam::channel::unbounded(); + let (send1, _recv1) = crossbeam::channel::unbounded(); let (send2, recv2) = crossbeam::channel::unbounded(); let handle = ChunkWorkerHandle { sender: send1, @@ -83,7 +83,7 @@ mod tests { let chunk_map = ChunkMap::new(); let pos = ChunkPosition::new(0, 0); - send2.send((pos, Ok(Chunk::new(pos)))); + send2.send((pos, Ok(Chunk::new(pos)))).unwrap(); let mut system = ChunkSystem; let mut world = World::new(); @@ -103,7 +103,7 @@ mod tests { #[test] fn test_load_chunk() { let (send1, recv1) = crossbeam::channel::unbounded(); - let (send2, recv2) = crossbeam::channel::unbounded(); + let (_send2, recv2) = crossbeam::channel::unbounded(); let handle = ChunkWorkerHandle { sender: send1, receiver: recv2, diff --git a/server/src/entity.rs b/server/src/entity.rs index 0e413b146..66699e80b 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -3,15 +3,13 @@ //! provide entity-specific components and systems. use crate::disconnect_player; -use crate::network::{ - send_packet_boxed_to_player, send_packet_to_player, NetworkComponent, PacketQueue, -}; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, }; -use feather_core::network::packet::{Packet, PacketType}; +use feather_core::network::packet::PacketType; use feather_core::world::Position; use feather_core::Gamemode; use specs::storage::BTreeStorage; @@ -56,7 +54,7 @@ impl<'a> System<'a> for PlayerUpdateSystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut ecomps, pcomps, packet_queue, netcomps, entities, lazy) = data; + let (mut ecomps, pcomps, packet_queue, netcomps, entities, _) = data; // Take movement packets let mut packets = vec![]; diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index ad170ac7a..92a0eb50c 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -268,7 +268,7 @@ fn handle_request( net: &NetworkComponent, config: &Config, player_count: &PlayerCount, - packet: &Request, + _packet: &Request, ) -> Result<(), Error> { if comp.stage != Stage::AwaitRequest { return Err(Error::InvalidPacket(PacketType::Request)); diff --git a/server/src/main.rs b/server/src/main.rs index 544a21610..32217e12a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -21,15 +21,10 @@ pub mod prelude; pub mod worldupdate; use crate::entity::EntityComponent; -use crate::initialhandler::InitialHandlerComponent; -use crate::network::{send_packet_to_player, NetworkComponent}; +use crate::network::send_packet_to_player; use feather_core::network::packet::implementation::DisconnectPlay; -use multimap::MultiMap; use prelude::*; -use specs::{ - Component, DenseVecStorage, Dispatcher, DispatcherBuilder, Entity, LazyUpdate, VecStorage, - World, WorldExt, -}; +use specs::{Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, WorldExt}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub const TPS: u64 = 20; @@ -192,6 +187,7 @@ mod tests { let config = Default::default(); let ioman = init_io_manager(&config); - let (world, dispatcher) = init_world(config, ioman); + let (world, mut dispatcher) = init_world(config, ioman); + dispatcher.dispatch(&world); } } diff --git a/server/src/network.rs b/server/src/network.rs index 5e49030a5..3878018cd 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -2,8 +2,7 @@ use crate::entity::{EntityComponent, PlayerComponent}; use crate::initialhandler::InitialHandlerComponent; use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; use crate::prelude::*; -use crate::{initialhandler as ih, TickCount}; -use feather_blocks::Block; +use crate::TickCount; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; @@ -123,7 +122,7 @@ impl<'a> System<'a> for NetworkSystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut netcomps, mut ihcomps, pcomps, packet_queue, ioman, entities, tick_count, lazy) = + let (mut netcomps, mut ihcomps, pcomps, packet_queue, ioman, entities, tick_count, _) = data; // Poll for new connections while let Ok(msg) = ioman.receiver.try_recv() { @@ -135,11 +134,11 @@ impl<'a> System<'a> for NetworkSystem { // Create entity let new_entity = entities.create(); - netcomps.insert(new_entity, netcomp); + netcomps.insert(new_entity, netcomp).unwrap(); // Create initial handler let ih = InitialHandlerComponent::new(); - ihcomps.insert(new_entity, ih); + ihcomps.insert(new_entity, ih).unwrap(); } _ => panic!("Network system received invalid message from IO listener"), } @@ -154,9 +153,9 @@ impl<'a> System<'a> for NetworkSystem { } ServerToWorkerMessage::NotifyDisconnect => { // TODO broadcast disconnect - entities.delete(player); + entities.delete(player).unwrap(); } - msg => panic!("Network system received invalid message from IO worker}"), + _ => panic!("Network system received invalid message from IO worker}"), } } } From fbdd2bfe8e8f2b096c75adceb90062b7abb41b1d Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 12:57:54 -0600 Subject: [PATCH 12/30] Bump version number --- Cargo.lock | 8 ++++---- core/Cargo.toml | 2 +- server/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12949db17..c9d2a83fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ name = "feather_client_core" version = "0.1.0" dependencies = [ - "feather_core 0.1.0", + "feather_core 0.3.0", ] [[package]] @@ -421,7 +421,7 @@ dependencies = [ [[package]] name = "feather_core" -version = "0.1.0" +version = "0.3.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -444,13 +444,13 @@ dependencies = [ [[package]] name = "feather_server" -version = "0.1.0" +version = "0.3.0" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "derive-new 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "feather_blocks 0.1.0", - "feather_core 0.1.0", + "feather_core 0.3.0", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/Cargo.toml b/core/Cargo.toml index 41be8d8fb..a0461074a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feather_core" -version = "0.1.0" +version = "0.3.0" authors = ["caelunshun "] edition = "2018" diff --git a/server/Cargo.toml b/server/Cargo.toml index 174c7f8b9..0409886e8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "feather_server" -version = "0.1.0" +version = "0.3.0" authors = ["caelunshun "] edition = "2018" From d0c10db18880e085dc956ea1a3783882456739ec Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 13:18:06 -0600 Subject: [PATCH 13/30] Fix panic on join --- server/src/initialhandler.rs | 44 ++++++++++++++++++------------------ server/src/main.rs | 5 +--- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 92a0eb50c..17623bbf2 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -72,8 +72,8 @@ impl<'a> System<'a> for InitialHandlerSystem { Entities<'a>, WriteStorage<'a, InitialHandlerComponent>, ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, PlayerComponent>, - ReadStorage<'a, EntityComponent>, + WriteStorage<'a, PlayerComponent>, + WriteStorage<'a, EntityComponent>, Read<'a, PacketQueue>, Read<'a, Config>, Write<'a, PlayerCount>, @@ -91,8 +91,8 @@ impl<'a> System<'a> for InitialHandlerSystem { entities, mut ih_comps, net_comps, - pcomps, - ecomps, + mut pcomps, + mut ecomps, packet_queue, config, mut player_count, @@ -119,8 +119,8 @@ impl<'a> System<'a> for InitialHandlerSystem { &entities, &mut ih_comps, &net_comps, - &pcomps, - &ecomps, + &mut pcomps, + &mut ecomps, &config, &mut player_count, &lazy, @@ -187,8 +187,8 @@ fn handle_packet( entities: &Entities, ihcomps: &mut WriteStorage, netcomps: &ReadStorage, - pcomps: &ReadStorage, - ecomps: &ReadStorage, + pcomps: &mut WriteStorage, + ecomps: &mut WriteStorage, config: &Config, player_count: &mut Write, lazy: &LazyUpdate, @@ -320,8 +320,8 @@ fn handle_login_start<'a>( config: &Config, player: Entity, ihcomps: &mut WriteStorage, - ecomps: &ReadStorage, - pcomps: &ReadStorage, + ecomps: &mut WriteStorage, + pcomps: &mut WriteStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, chunk_map: &ChunkMap, @@ -364,7 +364,7 @@ fn handle_login_start<'a>( profile_properties: vec![], gamemode: Gamemode::Creative, }; - lazy.insert(player, player_comp); + pcomps.insert(player, player_comp).unwrap(); let entity_comp = EntityComponent { position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), @@ -372,7 +372,7 @@ fn handle_login_start<'a>( display_name: comp.username.as_ref().unwrap().clone(), on_ground: true, }; - lazy.insert(player, entity_comp); + ecomps.insert(player, entity_comp).unwrap(); finish( player, @@ -395,8 +395,8 @@ fn handle_encryption_response( player: Entity, config: &Config, ihcomps: &mut WriteStorage, - ecomps: &ReadStorage, - pcomps: &ReadStorage, + ecomps: &mut WriteStorage, + pcomps: &mut WriteStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, entities: &Entities, @@ -476,7 +476,7 @@ fn handle_encryption_response( profile_properties: res.properties, gamemode: Gamemode::Creative, }; - lazy.insert(player, player_comp); + pcomps.insert(player, player_comp).unwrap(); let entity_comp = EntityComponent { position: Position::new(0.0, 64.0, 0.0, 0.0, 0.0), @@ -484,7 +484,7 @@ fn handle_encryption_response( display_name: comp.username.as_ref().unwrap().clone(), on_ground: true, }; - lazy.insert(player, entity_comp); + ecomps.insert(player, entity_comp).unwrap(); debug!("Authentication successful"); } else { return Err(Error::AuthenticationFailed); @@ -511,8 +511,8 @@ fn handle_encryption_response( fn finish( player: Entity, ihcomps: &mut WriteStorage, - ecomps: &ReadStorage, - pcomps: &ReadStorage, + ecomps: &WriteStorage, + pcomps: &WriteStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, entities: &Entities, @@ -555,8 +555,8 @@ fn finish( fn join_game( player: Entity, ihcomps: &mut WriteStorage, - ecomps: &ReadStorage, - pcomps: &ReadStorage, + ecomps: &WriteStorage, + pcomps: &WriteStorage, netcomps: &ReadStorage, chunk_handle: &ChunkWorkerHandle, entities: &Entities, @@ -618,8 +618,8 @@ fn join_game( pub fn complete_join_game( player: Entity, ihcomps: &mut WriteStorage, - ecomps: &ReadStorage, - pcomps: &ReadStorage, + ecomps: &WriteStorage, + pcomps: &WriteStorage, netcomps: &ReadStorage, entities: &Entities, ) { diff --git a/server/src/main.rs b/server/src/main.rs index 32217e12a..b2264987e 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -45,10 +45,7 @@ fn main() { info!("Starting Feather; please wait..."); - let io_manager = io::NetworkIoManager::start( - format!("127.0.0.1:{}", config.server.port).parse().unwrap(), - config.io.io_worker_threads, - ); + let io_manager = init_io_manager(&config); let (mut world, mut dispatcher) = init_world(config, io_manager); From 5428b0e1048e80a82d6efd45ec7cebe202773771 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 16:46:11 -0600 Subject: [PATCH 14/30] Add basic testing framework --- server/src/initialhandler.rs | 4 - server/src/main.rs | 2 + server/src/testframework.rs | 140 +++++++++++++++++++++++++++++++++++ server/src/worldupdate.rs | 97 +++++++++++++++--------- 4 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 server/src/testframework.rs diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 17623bbf2..6e59d66e4 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -77,10 +77,6 @@ impl<'a> System<'a> for InitialHandlerSystem { Read<'a, PacketQueue>, Read<'a, Config>, Write<'a, PlayerCount>, - // LazyUpdate is used to add player + entity - // components when a player joins. This prevents - // other systems from being blocked by the initial - // handler because of a dependency on WriteStorage. Read<'a, LazyUpdate>, Read<'a, ChunkMap>, Read<'a, ChunkWorkerHandle>, diff --git a/server/src/main.rs b/server/src/main.rs index b2264987e..3f35b14c6 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,6 +18,8 @@ pub mod initialhandler; pub mod io; pub mod network; pub mod prelude; +#[cfg(test)] +pub mod testframework; pub mod worldupdate; use crate::entity::EntityComponent; diff --git a/server/src/testframework.rs b/server/src/testframework.rs new file mode 100644 index 000000000..59d1629cd --- /dev/null +++ b/server/src/testframework.rs @@ -0,0 +1,140 @@ +//! Helper framework for writing unit tests. + +use crate::config::Config; +use crate::entity::{EntityComponent, PlayerComponent}; +use crate::io::ServerToWorkerMessage; +use crate::network::NetworkComponent; +use feather_core::network::packet::{Packet, PacketType}; +use feather_core::world::Position; +use feather_core::Gamemode; +use mio_extras::channel::{channel, Receiver, Sender}; +use rand::Rng; +use specs::{Builder, Dispatcher, Entity, World, WorldExt}; +use std::net::TcpListener; +use uuid::Uuid; + +/// Initializes a Specs world and dispatcher +/// using default configuration options and an +/// available server port. +pub fn init_world<'a, 'b>() -> (World, Dispatcher<'a, 'b>) { + let mut config: Config = Default::default(); + + config.server.port = find_open_port().unwrap(); + + let ioman = super::init_io_manager(&config); + + super::init_world(config, ioman) +} + +pub struct Player { + pub entity: Entity, + pub network_sender: Sender, + pub network_receiver: Receiver, +} + +/// Adds a player to the world, inserting +/// all the necessary components. Returns +/// a number of useful channels. +pub fn add_player(world: &mut World) -> Player { + let (ns1, nr1) = channel(); + let (ns2, nr2) = channel(); + let e = world + .create_entity() + .with(NetworkComponent::new(ns1, nr2)) + .with(PlayerComponent { + gamemode: Gamemode::Creative, + profile_properties: vec![], + }) + .with(EntityComponent { + uuid: Uuid::new_v4(), + on_ground: true, + position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), + display_name: "Test".to_string(), + }) + .build(); + + Player { + entity: e, + network_sender: ns2, + network_receiver: nr1, + } +} + +/// Asserts that the given player has received +/// a packet of the given type, returning the packet. +pub fn assert_packet_received(player: &Player, ty: PacketType) -> Box { + match player.network_receiver.try_recv().unwrap() { + ServerToWorkerMessage::SendPacket(pack) => { + assert_eq!(pack.ty(), ty); + pack + } + _ => panic!(), + } +} + +/// Retrieves up to `cap` packets sent to a player, if any. +/// If `cap` is set to `None`, all packets will be read. +/// +/// Note that this function consumes messages in +/// the network channel until enough packets have been read. +pub fn received_packets(player: &Player, cap: Option) -> Vec> { + let mut result = vec![]; + + while let Ok(msg) = player.network_receiver.try_recv() { + if let Some(cap) = cap.as_ref() { + if result.len() >= *cap { + break; + } + } + match msg { + ServerToWorkerMessage::SendPacket(pack) => result.push(pack), + _ => (), + } + } + + result +} + +/// Attempts to find an available port. +fn find_open_port() -> Option { + let start = rand::thread_rng().gen_range(10000, 30000); + (start..60000).find(|port| TcpListener::bind(("127.0.0.1", *port)).is_ok()) +} + +/// Heh... tests for the testing framework. +/// Not sure what the point of this is, since +/// all other tests would fail if the testing +/// framework didn't work. +mod tests { + use super::*; + use crate::entity::{EntityComponent, PlayerComponent}; + use crate::network::NetworkComponent; + + #[test] + fn test_find_open_port() { + let port = find_open_port().unwrap(); + println!("Found open port: {}", port); + assert!(TcpListener::bind(("127.0.0.1", port)).is_ok()); + } + + #[test] + fn test_init_world() { + // Check that initializing the world doesn't cause + // a panic. + let (w, mut d) = init_world(); + + // Check that running the dispatcher works fine + d.dispatch(&w); + } + + #[test] + fn test_add_player() { + let (mut w, _d) = init_world(); + + let entity = add_player(&mut w).entity; + + assert!(w.read_component::().get(entity).is_some()); + assert!(w.read_component::().get(entity).is_some()); + assert!(w.read_component::().get(entity).is_some()); + } +} diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 76aa650eb..b3831b571 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -2,7 +2,7 @@ //! including block update packets. use crate::disconnect_player; -use crate::entity::{check_player_joined, PlayerComponent}; +use crate::entity::PlayerComponent; use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::BlockChange; @@ -11,7 +11,7 @@ use feather_core::network::packet::PacketType; use feather_core::world::block::{Block, BlockToId}; use feather_core::world::{BlockPosition, ChunkMap}; use feather_core::Gamemode; -use specs::{Join, LazyUpdate, Read, ReadStorage, System, Write}; +use specs::{Entity, Join, LazyUpdate, Read, ReadStorage, System, Write}; /// System responsible for polling for PlayerDigging /// packets and the like and handling them accordingly. @@ -34,41 +34,36 @@ impl<'a> System<'a> for WorldUpdateSystem { // Handle packets for (player, _packet) in packets { - if !check_player_joined(player, &pcomps, &lazy) { - continue; - } - - let pcomp = pcomps.get(player).unwrap(); - let packet = cast_packet::(&_packet); - match packet.status { - PlayerDiggingStatus::StartedDigging => { - if pcomp.gamemode == Gamemode::Creative { - if handle_block_break(&mut chunk_map, packet.location, &netcomps, &pcomps) - .is_err() - { - disconnect_player( - player, - "Attempted to break block in unloaded chunk", - &lazy, - ); - } - } - } - PlayerDiggingStatus::FinishedDigging => { - if handle_block_break(&mut chunk_map, packet.location, &netcomps, &pcomps) - .is_err() - { - disconnect_player( - player, - "Attempted to break block in unloaded chunk", - &lazy, - ); - } + handle_player_digging(&mut chunk_map, packet, player, &netcomps, &pcomps, &lazy); + } + } +} + +/// Handles a Player Digging packet. +fn handle_player_digging( + chunk_map: &mut ChunkMap, + packet: &PlayerDigging, + player: Entity, + netcomps: &ReadStorage, + pcomps: &ReadStorage, + lazy: &LazyUpdate, +) { + let pcomp = pcomps.get(player).unwrap(); + match packet.status { + PlayerDiggingStatus::StartedDigging => { + if pcomp.gamemode == Gamemode::Creative { + if handle_block_break(chunk_map, packet.location, &netcomps, &pcomps).is_err() { + disconnect_player(player, "Attempted to break block in unloaded chunk", &lazy); } - status => warn!("Unhandled Player Digging status: {:?}", status), } } + PlayerDiggingStatus::FinishedDigging => { + if handle_block_break(chunk_map, packet.location, &netcomps, &pcomps).is_err() { + disconnect_player(player, "Attempted to break block in unloaded chunk", &lazy); + } + } + status => warn!("Unhandled Player Digging status: {:?}", status), } } @@ -98,3 +93,39 @@ fn broadcast_block_update( send_packet_to_player(net, block_update); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::testframework as t; + use specs::WorldExt; + + #[test] + fn test_broadcast_block_update() { + let (mut w, _) = t::init_world(); + + let player = t::add_player(&mut w); + let player2 = t::add_player(&mut w); + w.write_component::() + .remove(player2.entity) + .unwrap(); + + broadcast_block_update( + BlockPosition::new(0, 0, 0), + Block::Sand, + &w.read_component(), + &w.read_component(), + ); + + // Check that the joined player received block update but the unjoined + // player did not + let packet = t::assert_packet_received(&player, PacketType::BlockChange); + + let packet = cast_packet::(&packet); + assert_eq!(packet.location, BlockPosition::new(0, 0, 0)); + assert_eq!(packet.block_id, Block::Sand.block_state_id() as i32); + + let p2_packets = t::received_packets(&player2, None); + assert_eq!(p2_packets.len(), 0); + } +} From fd4b732257b06ccbf7c48e28bb0bafc325c3d078 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 23 Jul 2019 17:22:01 -0600 Subject: [PATCH 15/30] Complete worldupdate test suite --- core/src/world/mod.rs | 6 +- server/src/io/mod.rs | 2 +- server/src/network.rs | 2 +- server/src/testframework.rs | 9 ++- server/src/worldupdate.rs | 110 +++++++++++++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 8 deletions(-) diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 031c66ffe..00c1da73a 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -76,7 +76,7 @@ impl ChunkMap { /// Retrieves the block at the specified /// location. If the chunk in which the block /// exists is not laoded, `None` is returned. - pub fn block_at(&mut self, pos: BlockPosition) -> Option { + pub fn block_at(&self, pos: BlockPosition) -> Option { let chunk_pos = pos.chunk_pos(); if let Some(chunk) = self.chunk_at(chunk_pos) { @@ -115,9 +115,7 @@ impl ChunkMap { impl Default for ChunkMap { fn default() -> Self { - Self { - chunk_map: HashMap::new(), - } + Self::new() } } diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 5803f08d4..87d9bf447 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -81,6 +81,6 @@ impl NetworkIoManager { impl Default for NetworkIoManager { fn default() -> Self { - Self::start("127.0.0.1".parse().unwrap(), 8) + Self::start("127.0.0.1:25565".parse().unwrap(), 8) } } diff --git a/server/src/network.rs b/server/src/network.rs index 3878018cd..8972602cb 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -55,7 +55,7 @@ impl PacketQueue { } /// Adds a packet to the queue. - fn add_for_packet(&self, player: Entity, packet: Box) { + pub fn add_for_packet(&self, player: Entity, packet: Box) { let mut queue = self.queue.lock().unwrap(); let ordinal = packet.ty().ordinal(); diff --git a/server/src/testframework.rs b/server/src/testframework.rs index 59d1629cd..40b72ec5d 100644 --- a/server/src/testframework.rs +++ b/server/src/testframework.rs @@ -3,7 +3,7 @@ use crate::config::Config; use crate::entity::{EntityComponent, PlayerComponent}; use crate::io::ServerToWorkerMessage; -use crate::network::NetworkComponent; +use crate::network::{NetworkComponent, PacketQueue}; use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::Position; use feather_core::Gamemode; @@ -95,6 +95,13 @@ pub fn received_packets(player: &Player, cap: Option) -> Vec> result } +/// Adds a received packet to the packet queue +/// for a given player. +pub fn receive_packet(player: &Player, world: &World, packet: P) { + let queue = world.fetch_mut::(); + queue.add_for_packet(player.entity, Box::new(packet)); +} + /// Attempts to find an available port. fn find_open_port() -> Option { let start = rand::thread_rng().gen_range(10000, 30000); diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index b3831b571..0cc175c00 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -97,8 +97,116 @@ fn broadcast_block_update( #[cfg(test)] mod tests { use super::*; + use crate::entity::PlayerUpdateSystem; use crate::testframework as t; - use specs::WorldExt; + use feather_core::world::chunk::Chunk; + use feather_core::world::ChunkPosition; + use specs::{RunNow, WorldExt}; + + #[test] + fn test_system() { + let (mut w, _) = t::init_world(); + let player = t::add_player(&mut w); + + t::receive_packet( + &player, + &w, + PlayerDigging::new( + PlayerDiggingStatus::StartedDigging, + BlockPosition::new(0, 0, 0), + 0, + ), + ); + + let mut system = PlayerUpdateSystem; + system.run_now(&w); + } + + #[test] + fn test_handle_player_digging() { + let (mut w, _) = t::init_world(); + + let mut chunk = Chunk::new(ChunkPosition::new(0, 0)); + chunk.set_block_at(0, 0, 0, Block::Stone); + w.write_resource::() + .set_chunk_at(ChunkPosition::new(0, 0), chunk); + + let pos = BlockPosition::new(0, 0, 0); + + // Test with creative mode player + let player = t::add_player(&mut w); + + let packet = PlayerDigging::new(PlayerDiggingStatus::StartedDigging, pos, 0); + handle_player_digging( + &mut w.fetch_mut(), + &packet, + player.entity, + &w.read_component(), + &w.read_component(), + &w.read_resource(), + ); + + // Call lazily updated disconnect + w.maintain(); + + // Make sure player wasn't disconnected + let packets = t::received_packets(&player, None); + packets + .iter() + .for_each(|packet| assert_ne!(packet.ty(), PacketType::DisconnectPlay)); + + // Make sure player was notified of block update + let _block_change = packets + .iter() + .find(|packet| packet.ty() == PacketType::BlockChange) + .unwrap(); + let block_change = cast_packet::(&_block_change); + assert_eq!(block_change.location, pos); + assert_eq!(block_change.block_id, Block::Air.block_state_id() as i32); + + // Make sure block was actually updated + assert_eq!( + w.read_resource::().block_at(pos).unwrap(), + Block::Air + ); + } + + #[test] + fn test_handle_block_break() { + let (mut w, _) = t::init_world(); + + // Confirm that breaking block in unloaded chunk fails + let _player = t::add_player(&mut w); + assert!(handle_block_break( + &mut w.fetch_mut(), + BlockPosition::new(1024, 9, 1024), + &w.read_component(), + &w.read_component(), + ) + .is_err()); + + // Break block in known chunk + let _player = t::add_player(&mut w); + + let pos = ChunkPosition::new(0, 0); + let mut chunk = Chunk::new(pos); + chunk.set_block_at(0, 0, 0, Block::Stone); + w.write_resource::().set_chunk_at(pos, chunk); + + assert!(handle_block_break( + &mut w.write_resource(), + BlockPosition::new(0, 0, 0), + &w.read_component(), + &w.read_component(), + ) + .is_ok()); + assert_eq!( + w.read_resource::() + .block_at(BlockPosition::new(0, 0, 0)) + .unwrap(), + Block::Air + ); + } #[test] fn test_broadcast_block_update() { From c44c14c1f0d48a6e0342d24c6ccf00b9a3001fb8 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Wed, 24 Jul 2019 11:16:28 -0600 Subject: [PATCH 16/30] Move PlayerMovementUpdate system to player.rs --- server/config/feather.toml | 2 +- server/src/chunkclient.rs | 6 +- server/src/entity.rs | 131 ++++++----------------------------- server/src/initialhandler.rs | 47 +++++-------- server/src/main.rs | 15 ++-- server/src/network.rs | 20 ++++++ server/src/player.rs | 97 ++++++++++++++++++++++++++ server/src/worldupdate.rs | 9 ++- 8 files changed, 169 insertions(+), 158 deletions(-) create mode 100644 server/src/player.rs diff --git a/server/config/feather.toml b/server/config/feather.toml index 32a3ae501..4d575eefc 100644 --- a/server/config/feather.toml +++ b/server/config/feather.toml @@ -12,7 +12,7 @@ proxy_mode = "none" [server] online_mode = true -motd = " &lMesa called Jar Jar Binks!\n Mesa your humble servant!" +motd = "A Feather server" max_players = 16 default_gamemode = "survival" difficulty = "none" diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index b1df01c84..8867f36c7 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -18,9 +18,9 @@ impl Default for ChunkWorkerHandle { } /// System for receiving loaded chunks from the chunk worker thread. -pub struct ChunkSystem; +pub struct ChunkLoadSystem; -impl<'a> System<'a> for ChunkSystem { +impl<'a> System<'a> for ChunkLoadSystem { type SystemData = (Write<'a, ChunkMap>, Read<'a, ChunkWorkerHandle>); fn run(&mut self, data: Self::SystemData) { @@ -85,7 +85,7 @@ mod tests { let pos = ChunkPosition::new(0, 0); send2.send((pos, Ok(Chunk::new(pos)))).unwrap(); - let mut system = ChunkSystem; + let mut system = ChunkLoadSystem; let mut world = World::new(); world.insert(chunk_map); world.insert(handle); diff --git a/server/src/entity.rs b/server/src/entity.rs index 66699e80b..f1f6d0fa8 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -2,22 +2,21 @@ //! and `PlayerComponent`. In the future, will also //! provide entity-specific components and systems. -use crate::disconnect_player; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; -use feather_core::network::cast_packet; +use specs::storage::BTreeStorage; +use specs::{Component, Entities, Entity, LazyUpdate, ReadStorage, VecStorage}; +use uuid::Uuid; + use feather_core::network::packet::implementation::{ - EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, PlayerLook, PlayerPosition, - PlayerPositionAndLookServerbound, + EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, }; use feather_core::network::packet::PacketType; use feather_core::world::Position; use feather_core::Gamemode; -use specs::storage::BTreeStorage; -use specs::{ - Component, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, VecStorage, - WriteStorage, + +use crate::disconnect_player; +use crate::network::{ + send_packet_to_all_players, send_packet_to_player, NetworkComponent, PacketQueue, }; -use uuid::Uuid; pub struct PlayerComponent { pub profile_properties: Vec, @@ -39,92 +38,8 @@ impl Component for EntityComponent { type Storage = VecStorage; } -/// System for handling player update packets, -/// including movement and inventory packets. -pub struct PlayerUpdateSystem; - -impl<'a> System<'a> for PlayerUpdateSystem { - type SystemData = ( - WriteStorage<'a, EntityComponent>, - ReadStorage<'a, PlayerComponent>, - Read<'a, PacketQueue>, - ReadStorage<'a, NetworkComponent>, - Entities<'a>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut ecomps, pcomps, packet_queue, netcomps, entities, _) = data; - - // Take movement packets - let mut packets = vec![]; - packets.append(&mut packet_queue.for_packet(PacketType::PlayerPosition)); - packets.append(&mut packet_queue.for_packet(PacketType::PlayerPositionAndLookServerbound)); - packets.append(&mut packet_queue.for_packet(PacketType::PlayerLook)); - - // Handle movement packets - for (player, packet) in packets { - let ecomp = ecomps.get(player).unwrap(); - - let mut has_moved = false; - let mut has_looked = false; - - // Get position using packet and old position - let new_pos = match packet.ty() { - PacketType::PlayerPosition => { - has_moved = true; - let packet = cast_packet::(&packet); - - Position::new( - packet.x, - packet.feet_y, - packet.z, - ecomp.position.pitch, - ecomp.position.yaw, - ) - } - PacketType::PlayerLook => { - has_looked = true; - let packet = cast_packet::(&packet); - - Position::new( - ecomp.position.x, - ecomp.position.y, - ecomp.position.z, - packet.pitch, - packet.yaw, - ) - } - PacketType::PlayerPositionAndLookServerbound => { - has_moved = true; - has_looked = true; - let packet = cast_packet::(&packet); - - Position::new(packet.x, packet.feet_y, packet.z, packet.pitch, packet.yaw) - } - _ => unreachable!(), - }; - - // Broadcast position update - broadcast_entity_movement( - player, - ecomp.position, - new_pos, - has_moved, - has_looked, - &netcomps, - &pcomps, - &entities, - ); - - // Set new position - ecomps.get_mut(player).unwrap().position = new_pos; - } - } -} - /// Broadcasts to all joined players that an entity has moved. -fn broadcast_entity_movement( +pub fn broadcast_entity_movement( entity: Entity, old_pos: Position, new_pos: Position, @@ -149,18 +64,10 @@ fn broadcast_entity_movement( degrees_to_stops(new_pos.pitch), true, ); - for (net, _, e) in (netcomps, pcomps, entities).join() { - if entity != e { - send_packet_to_player(net, packet.clone()); - } - } + send_packet_to_all_players(netcomps, pcomps, entities, packet, Some(entity)); } else { let packet = EntityRelativeMove::new(entity.id() as i32, rx, ry, rz, true); - for (net, _, e) in (netcomps, pcomps, entities).join() { - if entity != e { - send_packet_to_player(net, packet.clone()); - } - } + send_packet_to_all_players(netcomps, pcomps, entities, packet, Some(entity)); } } else { let packet = EntityLook::new( @@ -169,11 +76,13 @@ fn broadcast_entity_movement( degrees_to_stops(new_pos.pitch), true, ); - for (net, _, e) in (netcomps, pcomps, entities).join() { - if entity != e { - send_packet_to_player(net, packet.clone()); - } - } + send_packet_to_all_players(netcomps, pcomps, entities, packet, Some(entity)); + } + + // Entity Head Look also needs to be sent if the entity turned its head + if has_looked { + let packet = EntityHeadLook::new(entity.id() as i32, degrees_to_stops(new_pos.yaw)); + send_packet_to_all_players(netcomps, pcomps, entities, packet, Some(entity)); } } @@ -203,5 +112,5 @@ pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i } pub fn degrees_to_stops(degs: f32) -> u8 { - ((degs / 360.0) * 256.) as u8 + ((degs / 360.0) * 256.0) as u8 } diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs index 6e59d66e4..8734daf20 100644 --- a/server/src/initialhandler.rs +++ b/server/src/initialhandler.rs @@ -1,24 +1,27 @@ -use crate::entity::{EntityComponent, PlayerComponent}; -use crate::network::{ - enable_compression_for_player, enable_encryption_for_player, get_player_initialization_packets, - send_packet_to_player, NetworkComponent, PacketQueue, -}; -use crate::prelude::*; -use feather_core::network::packet::{implementation::*, Packet, PacketType}; -use openssl::pkey::Private; -use openssl::rsa::{Padding, Rsa}; use std::fmt::Formatter; use std::str::FromStr; -use super::{PROTOCOL_VERSION, SERVER_VERSION}; -use crate::chunkclient::{load_chunk, ChunkWorkerHandle}; -use crate::{disconnect_player, PlayerCount}; use mojang_api::ServerAuthResponse; +use openssl::pkey::Private; +use openssl::rsa::{Padding, Rsa}; use specs::{ Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System, WorldExt, Write, WriteStorage, }; +use feather_core::network::packet::{implementation::*, Packet, PacketType}; + +use crate::chunkclient::{load_chunk, ChunkWorkerHandle}; +use crate::entity::{EntityComponent, PlayerComponent}; +use crate::network::{ + enable_compression_for_player, enable_encryption_for_player, get_player_initialization_packets, + send_packet_to_player, NetworkComponent, PacketQueue, +}; +use crate::prelude::*; +use crate::{disconnect_player, PlayerCount}; + +use super::{PROTOCOL_VERSION, SERVER_VERSION}; + const RSA_BITS: u32 = 1024; // Yes, very secure const VERIFY_TOKEN_LEN: usize = 4; @@ -574,29 +577,11 @@ fn join_game( send_packet_to_player(net, join_game); - // Send chunk data. If a chunk in the view distance - // hasn't been loaded, a load request will be queued. + // Load chunks and queue them for sending. let view_distance = config.server.view_distance as i32; for x in -view_distance..view_distance + 1 { for y in -view_distance..view_distance + 1 { let pos = ChunkPosition::new(x, y); - - if let Some(chunk) = chunk_map.chunk_at(pos) { - let chunk_data = ChunkData::new(chunk.clone()); - send_packet_to_player(net, chunk_data); - } else { - // Queue chunk for loading. - load_chunk(chunk_handle, pos); - // Make sure that the chunk is sent once loaded - lazy.exec_mut(move |world| { - world - .write_component::() - .get_mut(player) - .unwrap() - .chunks_to_send - .push(pos); - }); - } } } diff --git a/server/src/main.rs b/server/src/main.rs index 3f35b14c6..08dc29854 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,6 +17,7 @@ pub mod entity; pub mod initialhandler; pub mod io; pub mod network; +pub mod player; pub mod prelude; #[cfg(test)] pub mod testframework; @@ -105,7 +106,7 @@ fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Di world.insert(TickCount::default()); let mut dispatcher = DispatcherBuilder::new() - .with(chunkclient::ChunkSystem, "chunk_load", &[]) + .with(chunkclient::ChunkLoadSystem, "chunk_load", &[]) .with(network::NetworkSystem, "network", &[]) .with( initialhandler::InitialHandlerSystem, @@ -113,14 +114,14 @@ fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Di &["network", "chunk_load"], ) .with( - worldupdate::WorldUpdateSystem, - "world_update", - &["network", "chunk_load"], + worldupdate::PlayerDiggingSystem, + "player_digging", + &["network"], ) .with( - entity::PlayerUpdateSystem, - "player_update", - &["network", "chunk_load"], + player::PlayerMovementSystem, + "player_movement", + &["network"], ) .build(); diff --git a/server/src/network.rs b/server/src/network.rs index 8972602cb..bb0b21a74 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -248,6 +248,26 @@ pub fn get_player_initialization_packets( (player_info, spawn_player) } +/// Sends a packet to all (joined) players on the server, excluding +/// `neq`, if it exists. +pub fn send_packet_to_all_players( + net_comps: &ReadStorage, + player_comps: &ReadStorage, + entities: &Entities, + packet: P, + neq: Option, +) { + for (entity, net, _) in (entities, net_comps, player_comps).join() { + if let Some(e) = neq.as_ref() { + if *e == entity { + continue; // Exclude this entity + } + } + + send_packet_to_player(net, packet.clone()); + } +} + /// Sends a packet to the given player. pub fn send_packet_to_player(comp: &NetworkComponent, packet: P) { let _ = comp diff --git a/server/src/player.rs b/server/src/player.rs new file mode 100644 index 000000000..651f9650f --- /dev/null +++ b/server/src/player.rs @@ -0,0 +1,97 @@ +//! This module provides systems and components +//! relating to players, including player movement +//! and inventory handling. + +use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; +use crate::network::{NetworkComponent, PacketQueue}; +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::{ + PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, +}; +use feather_core::network::packet::{Packet, PacketType}; +use feather_core::world::Position; +use specs::{Entities, LazyUpdate, Read, ReadStorage, System, WriteStorage}; + +/// System for handling player movement +/// packets. +pub struct PlayerMovementSystem; + +impl<'a> System<'a> for PlayerMovementSystem { + type SystemData = ( + WriteStorage<'a, EntityComponent>, + ReadStorage<'a, PlayerComponent>, + Read<'a, PacketQueue>, + ReadStorage<'a, NetworkComponent>, + Entities<'a>, + Read<'a, LazyUpdate>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut ecomps, pcomps, packet_queue, netcomps, entities, _) = data; + + // Take movement packets + let mut packets = vec![]; + packets.append(&mut packet_queue.for_packet(PacketType::PlayerPosition)); + packets.append(&mut packet_queue.for_packet(PacketType::PlayerPositionAndLookServerbound)); + packets.append(&mut packet_queue.for_packet(PacketType::PlayerLook)); + + // Handle movement packets + for (player, packet) in packets { + let ecomp = ecomps.get(player).unwrap(); + + // Get position using packet and old position + let (new_pos, has_moved, has_looked) = new_pos_from_packet(ecomp.position, packet); + + // Broadcast position update + broadcast_entity_movement( + player, + ecomp.position, + new_pos, + has_moved, + has_looked, + &netcomps, + &pcomps, + &entities, + ); + + // Set new position + ecomps.get_mut(player).unwrap().position = new_pos; + } + } +} + +fn new_pos_from_packet(old_pos: Position, packet: Box) -> (Position, bool, bool) { + let mut has_looked = false; + let mut has_moved = false; + + let pos = match packet.ty() { + PacketType::PlayerPosition => { + has_moved = true; + let packet = cast_packet::(&packet); + + Position::new( + packet.x, + packet.feet_y, + packet.z, + old_pos.pitch, + old_pos.yaw, + ) + } + PacketType::PlayerLook => { + has_looked = true; + let packet = cast_packet::(&packet); + + Position::new(old_pos.x, old_pos.y, old_pos.z, packet.pitch, packet.yaw) + } + PacketType::PlayerPositionAndLookServerbound => { + has_moved = true; + has_looked = true; + let packet = cast_packet::(&packet); + + Position::new(packet.x, packet.feet_y, packet.z, packet.pitch, packet.yaw) + } + _ => panic!(), + }; + + (pos, has_moved, has_looked) +} diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 0cc175c00..599e6b7dc 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -14,10 +14,10 @@ use feather_core::Gamemode; use specs::{Entity, Join, LazyUpdate, Read, ReadStorage, System, Write}; /// System responsible for polling for PlayerDigging -/// packets and the like and handling them accordingly. -pub struct WorldUpdateSystem; +/// packets and handling them accordingly. +pub struct PlayerDiggingSystem; -impl<'a> System<'a> for WorldUpdateSystem { +impl<'a> System<'a> for PlayerDiggingSystem { type SystemData = ( Write<'a, ChunkMap>, ReadStorage<'a, PlayerComponent>, @@ -97,7 +97,6 @@ fn broadcast_block_update( #[cfg(test)] mod tests { use super::*; - use crate::entity::PlayerUpdateSystem; use crate::testframework as t; use feather_core::world::chunk::Chunk; use feather_core::world::ChunkPosition; @@ -118,7 +117,7 @@ mod tests { ), ); - let mut system = PlayerUpdateSystem; + let mut system = PlayerDiggingSystem; system.run_now(&w); } From 105becd64d7b2de7f2eccc6ef856586fbea9c9fe Mon Sep 17 00:00:00 2001 From: caelunshun Date: Wed, 24 Jul 2019 11:36:37 -0600 Subject: [PATCH 17/30] Add send_chunk_to_player function --- Cargo.lock | 1 + server/Cargo.toml | 1 + server/src/main.rs | 1 + server/src/player.rs | 93 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9d2a83fa..f8f12d844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,6 +461,7 @@ dependencies = [ "multimap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rnbt 0.1.0 (git+https://github.com/caelunshun/rnbt)", "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/server/Cargo.toml b/server/Cargo.toml index 0409886e8..36de94e77 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,6 +26,7 @@ mojang-api = "0.1.0" multimap = "0.5.0" rnbt = { git = "https://github.com/caelunshun/rnbt", version = "0.1.0" } specs = "0.15.0" +rayon = "1.1.0" [dev-dependencies] mockers = "0.21.0" diff --git a/server/src/main.rs b/server/src/main.rs index 08dc29854..dbabcebb7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -123,6 +123,7 @@ fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Di "player_movement", &["network"], ) + .with(player::ChunkSendSystem, "chunk_send", &["chunk_load"]) .build(); dispatcher.setup(&mut world); diff --git a/server/src/player.rs b/server/src/player.rs index 651f9650f..e26308409 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -2,15 +2,23 @@ //! relating to players, including player movement //! and inventory handling. +use crate::chunkclient::ChunkWorkerHandle; use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; -use crate::network::{NetworkComponent, PacketQueue}; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ - PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, + ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, }; use feather_core::network::packet::{Packet, PacketType}; -use feather_core::world::Position; -use specs::{Entities, LazyUpdate, Read, ReadStorage, System, WriteStorage}; +use feather_core::world::chunk::Chunk; +use feather_core::world::{ChunkMap, ChunkPosition, Position}; +use hashbrown::HashSet; +use rayon::prelude::*; +use specs::storage::BTreeStorage; +use specs::{ + Component, Entities, Entity, LazyUpdate, ParJoin, Read, ReadStorage, System, WorldExt, + WriteStorage, +}; /// System for handling player movement /// packets. @@ -95,3 +103,80 @@ fn new_pos_from_packet(old_pos: Position, packet: Box) -> (Position, boo (pos, has_moved, has_looked) } + +/// Component storing what chunks are pending +/// to send to a player. +#[derive(Clone, Debug)] +pub struct ChunkPendingComponent { + pub pending: HashSet, +} + +impl Component for ChunkPendingComponent { + type Storage = BTreeStorage; +} + +/// System for sending chunks to players once they're loaded. +pub struct ChunkSendSystem; + +impl<'a> System<'a> for ChunkSendSystem { + type SystemData = ( + WriteStorage<'a, ChunkPendingComponent>, + ReadStorage<'a, NetworkComponent>, + Read<'a, ChunkMap>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut pendings, netcomps, chunk_map) = data; + + (&mut pendings, &netcomps) + .par_join() + .for_each(|(pending, net)| { + let mut to_remove = vec![]; + for pos in &pending.pending { + if let Some(chunk) = chunk_map.chunk_at(*pos) { + // Chunk has been loaded - send it + to_remove.push(*pos); + + let packet = ChunkData::new(chunk.clone()); + send_packet_to_player(net, packet); + } + } + + to_remove.into_iter().for_each(|pos| { + pending.pending.remove(&pos); + }); + }); + } +} + +fn send_chunk_data(chunk: &Chunk, net: &NetworkComponent) { + let packet = ChunkData::new(chunk.clone()); + send_packet_to_player(net, packet); +} + +/// Attempts to send the chunk at the given position to +/// the given player. If the chunk is not loaded, it will +/// be loaded and sent at a later time as soon as it is +/// loaded. +pub fn send_chunk_to_player( + chunk_pos: ChunkPosition, + net: &NetworkComponent, + player: Entity, + chunk_map: &ChunkMap, + chunk_handle: &ChunkWorkerHandle, + lazy: &LazyUpdate, +) { + if let Some(chunk) = chunk_map.chunk_at(chunk_pos) { + send_chunk_data(chunk, net); + } else { + // Queue for loading + lazy.exec_mut(move |world| { + world + .write_component::() + .get_mut(player) + .unwrap() + .pending + .insert(chunk_pos); + }); + } +} From 7aaad10449476e4f93c0b7efb0ac0e365296c7d5 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Thu, 25 Jul 2019 12:12:17 -0600 Subject: [PATCH 18/30] Start moving initial handler to IO worker thread --- core/src/network/packet/implementation.rs | 6 +- server/src/initialhandler.rs | 654 ---------------------- server/src/io/initialhandler.rs | 588 +++++++++++++++++++ server/src/io/mod.rs | 8 +- server/src/io/worker.rs | 13 +- server/src/main.rs | 8 +- server/src/network.rs | 13 - server/src/player.rs | 3 +- 8 files changed, 598 insertions(+), 695 deletions(-) delete mode 100644 server/src/initialhandler.rs create mode 100644 server/src/io/initialhandler.rs diff --git a/core/src/network/packet/implementation.rs b/core/src/network/packet/implementation.rs index 4c96cfa25..1e9ab34f3 100644 --- a/core/src/network/packet/implementation.rs +++ b/core/src/network/packet/implementation.rs @@ -553,9 +553,7 @@ pub struct DisconnectLogin { #[derive(Default, AsAny, new, Clone)] pub struct EncryptionRequest { pub server_id: String, - pub public_key_len: VarInt, pub public_key: Vec, - pub verify_token_len: VarInt, pub verify_token: Vec, } @@ -567,10 +565,10 @@ impl Packet for EncryptionRequest { fn write_to(&self, mut buf: &mut ByteBuf) { buf.write_string(self.server_id.as_str()); - buf.write_var_int(self.public_key_len); + buf.write_var_int(self.public_key.len()) as i32; buf.write(&self.public_key); - buf.write_var_int(self.verify_token_len); + buf.write_var_int(self.verify_token.len() as i32); buf.write(&self.verify_token); } diff --git a/server/src/initialhandler.rs b/server/src/initialhandler.rs deleted file mode 100644 index 8734daf20..000000000 --- a/server/src/initialhandler.rs +++ /dev/null @@ -1,654 +0,0 @@ -use std::fmt::Formatter; -use std::str::FromStr; - -use mojang_api::ServerAuthResponse; -use openssl::pkey::Private; -use openssl::rsa::{Padding, Rsa}; -use specs::{ - Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System, - WorldExt, Write, WriteStorage, -}; - -use feather_core::network::packet::{implementation::*, Packet, PacketType}; - -use crate::chunkclient::{load_chunk, ChunkWorkerHandle}; -use crate::entity::{EntityComponent, PlayerComponent}; -use crate::network::{ - enable_compression_for_player, enable_encryption_for_player, get_player_initialization_packets, - send_packet_to_player, NetworkComponent, PacketQueue, -}; -use crate::prelude::*; -use crate::{disconnect_player, PlayerCount}; - -use super::{PROTOCOL_VERSION, SERVER_VERSION}; - -const RSA_BITS: u32 = 1024; // Yes, very secure - -const VERIFY_TOKEN_LEN: usize = 4; - -#[derive(Debug, Eq, PartialEq)] -pub enum Stage { - AwaitHandshake, - - AwaitRequest, - AwaitPing, - - AwaitLoginStart, - AwaitEncryptionResponse, - AwaitChunkLoad, -} - -pub struct InitialHandlerComponent { - pub stage: Stage, - rsa_key: Option>, - verify_token: [u8; VERIFY_TOKEN_LEN], - /// Sent to server in Login Start - username: Option, -} - -impl Component for InitialHandlerComponent { - type Storage = HashMapStorage; -} - -impl Default for InitialHandlerComponent { - fn default() -> Self { - Self { - stage: Stage::AwaitHandshake, - rsa_key: None, - verify_token: rand::random(), - username: None, - } - } -} - -impl InitialHandlerComponent { - pub fn new() -> Self { - Default::default() - } -} - -/// System for handling the login sequence. -pub struct InitialHandlerSystem; - -impl<'a> System<'a> for InitialHandlerSystem { - type SystemData = ( - Entities<'a>, - WriteStorage<'a, InitialHandlerComponent>, - ReadStorage<'a, NetworkComponent>, - WriteStorage<'a, PlayerComponent>, - WriteStorage<'a, EntityComponent>, - Read<'a, PacketQueue>, - Read<'a, Config>, - Write<'a, PlayerCount>, - Read<'a, LazyUpdate>, - Read<'a, ChunkMap>, - Read<'a, ChunkWorkerHandle>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - entities, - mut ih_comps, - net_comps, - mut pcomps, - mut ecomps, - packet_queue, - config, - mut player_count, - lazy, - chunk_map, - chunk_handle, - ) = data; - - let mut packets = vec![]; - packets.append(&mut packet_queue.for_packet(PacketType::Handshake)); - packets.append(&mut packet_queue.for_packet(PacketType::LoginStart)); - packets.append(&mut packet_queue.for_packet(PacketType::Request)); - packets.append(&mut packet_queue.for_packet(PacketType::Ping)); - packets.append(&mut packet_queue.for_packet(PacketType::EncryptionResponse)); - - for (player, packet) in packets { - if !player_check(player, &ih_comps, &lazy) { - continue; // Bad player - kicked - } - - if let Err(e) = handle_packet( - player, - packet, - &entities, - &mut ih_comps, - &net_comps, - &mut pcomps, - &mut ecomps, - &config, - &mut player_count, - &lazy, - &chunk_map, - &chunk_handle, - ) { - disconnect_player(player, "InitialHandler: error occurred", &lazy); - info!("InitialHandler: player was kicked: {}", e); - } - } - - // TODO send chunks - } -} - -/// Verifies that the given player is still in the login phase, -/// disconnecting them and returning `false` if they are not. -fn player_check( - player: Entity, - ih_comps: &WriteStorage, - lazy: &LazyUpdate, -) -> bool { - if !ih_comps.contains(player) { - disconnect_player(player, "Sent invalid packet while in play", lazy); - return false; - } - true -} - -/// An eror which occurred during initial handling -/// for a player -enum Error { - InvalidPacket(PacketType), - MalformedData(String), - InvalidProtocolVersion(u32, u32), - AuthenticationFailed, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { - match self { - Error::InvalidPacket(ty) => f - .write_str(&format!("Sent invalid packet type {:?}", ty)) - .unwrap(), - Error::MalformedData(details) => f - .write_str(&format!("Sent invalid data: {}", details)) - .unwrap(), - Error::InvalidProtocolVersion(server, client) => f - .write_str(&format!( - "Protocol versions do not match: client is on {}; server is on {}", - client, server - )) - .unwrap(), - Error::AuthenticationFailed => f.write_str("Authentication failed").unwrap(), - }; - - Ok(()) - } -} - -fn handle_packet( - player: Entity, - packet: Box, - entities: &Entities, - ihcomps: &mut WriteStorage, - netcomps: &ReadStorage, - pcomps: &mut WriteStorage, - ecomps: &mut WriteStorage, - config: &Config, - player_count: &mut Write, - lazy: &LazyUpdate, - chunk_map: &ChunkMap, - chunk_handle: &ChunkWorkerHandle, -) -> Result<(), Error> { - let ihcomp = ihcomps.get_mut(player).unwrap(); - let netcomp = netcomps.get(player).unwrap(); - - match packet.ty() { - PacketType::Handshake => handle_handshake(ihcomp, cast_packet::(&packet))?, - PacketType::Request => handle_request( - ihcomp, - netcomp, - config, - player_count, - cast_packet::(&packet), - )?, - PacketType::Ping => { - handle_ping(ihcomp, netcomp, player, lazy, cast_packet::(&packet))? - } - PacketType::LoginStart => handle_login_start( - netcomp, - config, - player, - ihcomps, - ecomps, - pcomps, - netcomps, - chunk_handle, - chunk_map, - entities, - lazy, - cast_packet::(&packet), - )?, - PacketType::EncryptionResponse => handle_encryption_response( - player, - config, - ihcomps, - ecomps, - pcomps, - netcomps, - chunk_handle, - entities, - lazy, - chunk_map, - cast_packet::(&packet), - )?, - _ => panic!("Invalid packet"), - } - - Ok(()) -} - -fn handle_handshake(comp: &mut InitialHandlerComponent, packet: &Handshake) -> Result<(), Error> { - if comp.stage != Stage::AwaitHandshake { - return Err(Error::InvalidPacket(PacketType::Handshake)); - } - - if packet.protocol_version != PROTOCOL_VERSION && packet.next_state != HandshakeState::Status { - return Err(Error::InvalidProtocolVersion( - PROTOCOL_VERSION, - packet.protocol_version, - )); - } - - match packet.next_state { - HandshakeState::Login => comp.stage = Stage::AwaitLoginStart, - HandshakeState::Status => comp.stage = Stage::AwaitRequest, - } - - Ok(()) -} - -fn handle_request( - comp: &mut InitialHandlerComponent, - net: &NetworkComponent, - config: &Config, - player_count: &PlayerCount, - _packet: &Request, -) -> Result<(), Error> { - if comp.stage != Stage::AwaitRequest { - return Err(Error::InvalidPacket(PacketType::Request)); - } - - let payload = json!({ - "version": { - "name": SERVER_VERSION, - "protocol": PROTOCOL_VERSION, - }, - "players": { - "max": config.server.max_players, - "online": player_count.0, - }, - "description": config.server.motd, - }); - - let response = Response::new(payload.to_string()); - - comp.stage = Stage::AwaitPing; - - send_packet_to_player(net, response); - - Ok(()) -} - -fn handle_ping( - comp: &mut InitialHandlerComponent, - net: &NetworkComponent, - player: Entity, - lazy: &LazyUpdate, - packet: &Ping, -) -> Result<(), Error> { - if comp.stage != Stage::AwaitPing { - return Err(Error::InvalidPacket(PacketType::Ping)); - } - - let pong = Pong::new(packet.payload); - send_packet_to_player(net, pong); - - lazy.exec_mut(move |world| world.delete_entity(player).unwrap()); - debug!("Status handling success"); - - Ok(()) -} - -fn handle_login_start<'a>( - net: &NetworkComponent, - config: &Config, - player: Entity, - ihcomps: &mut WriteStorage, - ecomps: &mut WriteStorage, - pcomps: &mut WriteStorage, - netcomps: &ReadStorage, - chunk_handle: &ChunkWorkerHandle, - chunk_map: &ChunkMap, - entities: &Entities, - lazy: &LazyUpdate, - packet: &LoginStart, -) -> Result<(), Error> { - let comp = ihcomps.get_mut(player).unwrap(); - comp.username = Some(packet.username.clone()); - - if comp.stage != Stage::AwaitLoginStart { - return Err(Error::InvalidPacket(PacketType::Ping)); - } - - // If in online mode, enable encryption - if config.server.online_mode { - let rsa_key = Rsa::generate(RSA_BITS).unwrap(); - - let key_bytes = rsa_key.public_key_to_der().unwrap(); - - let mut verify_token = vec![]; - verify_token.extend_from_slice(&comp.verify_token); - - let encryption_request = EncryptionRequest::new( - "".to_string(), // Server ID - always empty nowadays - key_bytes.len() as i32, - key_bytes, - VERIFY_TOKEN_LEN as i32, - verify_token, - ); - - comp.rsa_key = Some(rsa_key); - comp.stage = Stage::AwaitEncryptionResponse; - - send_packet_to_player(net, encryption_request); - } else { - // Login completed - initialize entity and player components - // This would otherwise be done in `handle_encryption_response` - let player_comp = PlayerComponent { - profile_properties: vec![], - gamemode: Gamemode::Creative, - }; - pcomps.insert(player, player_comp).unwrap(); - - let entity_comp = EntityComponent { - position: Position::new(0.0, 0.0, 0.0, 0.0, 0.0), - uuid: Uuid::new_v4(), - display_name: comp.username.as_ref().unwrap().clone(), - on_ground: true, - }; - ecomps.insert(player, entity_comp).unwrap(); - - finish( - player, - ihcomps, - ecomps, - pcomps, - netcomps, - chunk_handle, - entities, - lazy, - chunk_map, - config, - ); - } - - Ok(()) -} - -fn handle_encryption_response( - player: Entity, - config: &Config, - ihcomps: &mut WriteStorage, - ecomps: &mut WriteStorage, - pcomps: &mut WriteStorage, - netcomps: &ReadStorage, - chunk_handle: &ChunkWorkerHandle, - entities: &Entities, - lazy: &LazyUpdate, - chunk_map: &ChunkMap, - packet: &EncryptionResponse, -) -> Result<(), Error> { - let comp = ihcomps.get_mut(player).unwrap(); - if comp.stage != Stage::AwaitEncryptionResponse { - return Err(Error::InvalidPacket(PacketType::Ping)); - } - - let net = netcomps.get(player).unwrap(); - - let rsa = comp.rsa_key.as_ref().unwrap(); - - let secret: [u8; 16] = { - let mut buf = vec![0u8; rsa.size() as usize]; - - if let Ok(amnt) = rsa.private_decrypt(&packet.secret, &mut buf, Padding::PKCS1) { - if amnt != 16 { - return Err(Error::MalformedData(format!( - "Invalid shared secret length {}", - amnt - ))); - } - - let mut res = [0u8; 16]; - for (i, val) in buf[..16].iter().enumerate() { - res[i] = *val; - } - res - } else { - return Err(Error::MalformedData( - "Error decrypting shared secret".to_string(), - )); - } - }; - - let verify_token: [u8; VERIFY_TOKEN_LEN] = { - let mut buf = vec![0u8; rsa.size() as usize]; - - if let Ok(amnt) = rsa.private_decrypt(&packet.verify_token, &mut buf, Padding::PKCS1) { - if amnt != VERIFY_TOKEN_LEN { - return Err(Error::MalformedData(format!( - "Invalid verify token length {}", - amnt - ))); - } - - let mut res = [0u8; VERIFY_TOKEN_LEN]; - for (i, val) in buf[..VERIFY_TOKEN_LEN].iter().enumerate() { - res[i] = *val; - } - res - } else { - return Err(Error::MalformedData( - "Error decrypting verify token".to_string(), - )); - } - }; - - if !compare_verify_tokens(comp.verify_token.clone(), verify_token) { - return Err(Error::MalformedData( - "Verify tokens do not match".to_string(), - )); - } - - // Authenticate - let auth_res = authenticate( - secret.clone(), - &comp.rsa_key.as_ref().unwrap().public_key_to_der().unwrap(), - comp.username.as_ref().unwrap(), - ); - if let Ok(res) = auth_res { - let player_comp = PlayerComponent { - profile_properties: res.properties, - gamemode: Gamemode::Creative, - }; - pcomps.insert(player, player_comp).unwrap(); - - let entity_comp = EntityComponent { - position: Position::new(0.0, 64.0, 0.0, 0.0, 0.0), - uuid: Uuid::from_str(&res.id).unwrap(), - display_name: comp.username.as_ref().unwrap().clone(), - on_ground: true, - }; - ecomps.insert(player, entity_comp).unwrap(); - debug!("Authentication successful"); - } else { - return Err(Error::AuthenticationFailed); - } - - enable_encryption_for_player(net, secret); - - finish( - player, - ihcomps, - ecomps, - pcomps, - netcomps, - chunk_handle, - entities, - lazy, - chunk_map, - config, - ); - - Ok(()) -} - -fn finish( - player: Entity, - ihcomps: &mut WriteStorage, - ecomps: &WriteStorage, - pcomps: &WriteStorage, - netcomps: &ReadStorage, - chunk_handle: &ChunkWorkerHandle, - entities: &Entities, - lazy: &LazyUpdate, - chunk_map: &ChunkMap, - config: &Config, -) { - let net = netcomps.get(player).unwrap(); - // Enable compression if needed - let threshold = config.io.compression_threshold; - if threshold > 0 { - let set_compression = SetCompression::new(threshold); - send_packet_to_player(net, set_compression); - - enable_compression_for_player(net, threshold as usize); - } - - let ecomp = ecomps.get(player).unwrap(); - - let login_success = LoginSuccess::new( - ecomp.uuid.to_hyphenated_ref().to_string(), - ecomp.display_name.to_string(), - ); - send_packet_to_player(net, login_success); - - join_game( - player, - ihcomps, - ecomps, - pcomps, - netcomps, - chunk_handle, - entities, - lazy, - chunk_map, - config, - ); -} - -fn join_game( - player: Entity, - ihcomps: &mut WriteStorage, - ecomps: &WriteStorage, - pcomps: &WriteStorage, - netcomps: &ReadStorage, - chunk_handle: &ChunkWorkerHandle, - entities: &Entities, - lazy: &LazyUpdate, - chunk_map: &ChunkMap, - config: &Config, -) { - let join_game = JoinGame::new( - player.id() as i32, - Gamemode::Creative.get_id(), - Dimension::Overwold.get_id(), - Difficulty::Medium.get_id(), - 0, - "default".to_string(), - false, - ); - - let net = netcomps.get(player).unwrap(); - - send_packet_to_player(net, join_game); - - // Load chunks and queue them for sending. - let view_distance = config.server.view_distance as i32; - for x in -view_distance..view_distance + 1 { - for y in -view_distance..view_distance + 1 { - let pos = ChunkPosition::new(x, y); - } - } - - if net.chunks_to_send.is_empty() { - complete_join_game(player, ihcomps, ecomps, pcomps, netcomps, entities); - } else { - // If not all chunks have been sent, we need to wait - // until they're loaded and sent before spawning the player. - // The network system will call `complete_join_game` upon - // sending all chunks. - ihcomps.get_mut(player).unwrap().stage = Stage::AwaitChunkLoad; - } -} - -pub fn complete_join_game( - player: Entity, - ihcomps: &mut WriteStorage, - ecomps: &WriteStorage, - pcomps: &WriteStorage, - netcomps: &ReadStorage, - entities: &Entities, -) { - // Send spawn position + player position - // TODO proper persistence - let net = netcomps.get(player).unwrap(); - - let spawn_position = SpawnPosition::new(BlockPosition::new(0, 64, 0)); - send_packet_to_player(net, spawn_position); - - let pos_and_look = PlayerPositionAndLookClientbound::new(0.0, 64.0, 0.0, 0.0, 0.0, 0, 0); - send_packet_to_player(net, pos_and_look); - - // Send other players on the server - for (ecomp, pcomp, net, other_player) in (ecomps, pcomps, netcomps, entities).join() { - let (player_info, spawn_player) = - get_player_initialization_packets(ecomp, pcomp, other_player); - send_packet_to_player(net, player_info); - send_packet_to_player(net, spawn_player); - } - - ihcomps.remove(player); - - //broadcast_player_join(player, netcomps, pcomps, ecomps); - - info!("A player joined the game"); -} - -/// Authenticates a client. -fn authenticate(secret: [u8; 16], pubkey: &[u8], username: &str) -> Result { - let server_hash = mojang_api::server_hash("", secret, pubkey); - let res = mojang_api::server_auth(username, &server_hash); - - res.map_err(|_| ()) -} - -fn compare_verify_tokens(x: [u8; VERIFY_TOKEN_LEN], y: [u8; VERIFY_TOKEN_LEN]) -> bool { - for i in 0..VERIFY_TOKEN_LEN { - if x[i] != y[i] { - return false; - } - } - - true -} - -pub fn disconnect_login(net: &NetworkComponent, reason: &str) { - let packet = DisconnectLogin::new(json!({ "text": reason }).to_string()); - - send_packet_to_player(net, packet); -} diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs new file mode 100644 index 000000000..723a52fe5 --- /dev/null +++ b/server/src/io/initialhandler.rs @@ -0,0 +1,588 @@ +//! The initial handler is responsible for +//! handling new connections and getting +//! through the login sequence. After login +//! is completed, control is handed over to the server +//! thread, which is responsible for sending chunks/inventory/ +//! players and then spawning the player. +//! +//! The initial handler is also responsible for handling +//! server list pings. To do this, it shares an `Arc` +//! representing the player count with the server. +//! +//! The initial handler runs on the IO worker thread. +//! This is done to ensure minimal latency in packet handling, +//! speeding up the login process and making the latency calculation in +//! the server list ping as low as possible. + +use crate::config::Config; +use crate::io::NewClientInfo; +use crate::{PlayerCount, PROTOCOL_VERSION, SERVER_VERSION}; +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::{ + EncryptionRequest, EncryptionResponse, Handshake, HandshakeState, LoginStart, LoginSuccess, + Ping, Pong, Request, Response, +}; +use feather_core::network::packet::PacketType::DisconnectLogin; +use feather_core::network::packet::{Packet, PacketType}; +use openssl::pkey::Private; +use openssl::rsa::Rsa; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use uuid::Uuid; + +/// The key used for symmetric encryption. +pub type Key = [u8; 16]; +/// The verify token used to ensure that encryption +/// is working correctly. +type VerifyToken = [u8; 4]; + +/// The number of bits used for the RSA key. +const RSA_KEY_BITS: u32 = 1024; + +/// The type returned for when a player has completed the login process. +pub struct JoinResult { + pub username: String, + pub uuid: Uuid, + pub props: Vec, +} + +/// An initial handler for a connection. +/// +/// When a packet is received from the client this initial +/// handler is registered with, `handle_packet` should be called. +/// This function runs all the necessary code to handle the +/// login sequence or the server list ping. +/// +/// The initial handler is able to communicate with the worker +/// implementation by exposing a number of methods which are +/// used to poll for actions. For example, `packets_to_send` returns +/// a vector of packets that should be sent to the client, and `should_disconnect` +/// returns whether the client should be disconnected. +pub struct InitialHandler { + /// A queue of packets to send to the client. + /// When `packets_to_send` is called, this queue + /// is drained. + packets_to_send: Vec>, + /// Whether the client registered with this + /// initial handler should be disconnected. + should_disconnect: bool, + + /// If set to a value, indicates that encryption + /// should be enabled with the given key. + key: Option, + /// If set to a value, indicates that compression + /// should be enabled with the given threshold. + compression_threshold: Option, + + /// The 1024-bit RSA key used for key exchange + /// with this client. + rsa: Rsa, + /// The verify token generated for this exchange. + verify_token: VerifyToken, + + /// The server's configuration. + config: Arc, + /// The server's player count. + player_count: Arc, + + /// The username of the player, sent + /// in Login Start. + username: Option, + + /// The player info, set to `Some` once + /// the initial handler is finished and + /// the player should join. + info: Option, + + /// The stage of this initial handler. + stage: Stage, +} + +impl InitialHandler { + pub fn new(config: Arc, player_count: Arc) -> Self { + Self { + packets_to_send: vec![], + should_disconnect: false, + + key: None, + compression_threshold: None, + + rsa: Rsa::generate(RSA_KEY_BITS).unwrap(), + verify_token: rand::random(), + + config, + player_count, + + username: None, + + info: None, + + stage: Stage::AwaitHandshake, + } + } + + /// Notifies this initial handler of a packet + /// received from the client. After calling this + /// function, `packets_to_send`, `should_disconnect`, + /// `should_enable_compression`, `should_enable_encryption`, + /// and `should_join`, in that order, + /// should be called to handle those various events. + pub fn handle_packet(&mut self, packet: Box) { + if self.stage == Stage::Finished { + panic!("Called InitialHandler::handle_packet() after completion"); + } + + if let Err(e) = _handle_packet(self, packet) { + // Disconnect + disconnect_login(self, &format!("{}", e)); + } + } + + /// Returns a vector of packets to send to + /// the client. The internal queue is flushed + /// upon calling this function, meaning that + /// future calls to this function will return + /// an empty vector until `handle_packet` is called + /// again. + pub fn packets_to_send(&mut self) -> Vec> { + let mut new_vec = vec![]; + std::mem::swap(&mut new_vec, &mut self.packets_to_send); + + new_vec + } + + /// Returns a boolean indicating whether + /// the client should be disconnected. + /// + /// If `true` is returned, the initial + /// handler should not be used further. + /// It should be dropped. + pub fn should_disconnect(&self) -> bool { + self.should_disconnect + } + + /// Returns an option indicating whether encryption should + /// be enabled. + /// + /// If `Some(key)` is returned, encryption should be enabled + /// with the given key. Future calls to this function will + /// return `None` to avoid enabling encryption multiple times. + /// + /// If `None` is returned, nothing needs to be done. + pub fn should_enable_encryption(&mut self) -> Option { + if let Some(key) = self.key { + self.key = None; + Some(key) + } else { + None + } + } + + /// Returns an option indicating whether compression should + /// be enabled. + /// + /// If `Some(threshold)` is returned, compression should be enabled + /// with the given threshold. Future calls to this function will + /// return `None` in order to avoid enabling compression multiple + /// times. + /// + /// if `None` is returned, nothing needs to be done. + pub fn should_enable_compression(&mut self) -> Option { + if let Some(threshold) = self.compression_threshold { + self.compression_threshold = None; + Some(threshold) + } else { + None + } + } + + /// Returns whether the player should be joined. + /// + /// If `Some` is returned, no further calls to `handle_packet` + /// should be made. + pub fn should_join(&self) -> Option { + self.info + } +} + +/// Handles a packet, returning `Err` if the player +/// should be disconnected. +fn _handle_packet(ih: &mut InitialHandler, packet: Box) -> Result<(), Error> { + // Find packet type and forward to correct function + match packet.ty() { + PacketType::Handshake => handle_handshake(ih, cast_packet::(&packet))?, + PacketType::Request => handle_request(ih, cast_packet::(&packet))?, + PacketType::Ping => handle_ping(ih, cast_packet::(&packet))?, + PacketType::LoginStart => handle_login_start(ih, cast_packet::(&packet))?, + PacketType::EncryptionResponse => { + handle_encryption_response(ih, cast_packet::(&packet))? + } + ty => return Err(Error::InvalidPacket(ty, ih.stage)), + } + + Ok(()) +} + +fn handle_handshake(ih: &mut InitialHandler, packet: &Handshake) -> Result<(), Error> { + check_stage(ih, Stage::AwaitHandshake, packet.ty())?; + + ih.stage = match packet.next_state { + HandshakeState::Status => Stage::AwaitRequest, + HandshakeState::Login => { + // While status requests can use differing + // protocol versions, a client + // needs to have a matching protocol version + // to log in. + if packet.protocol_version != PROTOCOL_VERSION { + return Err(Error::InvalidProtocol(packet.protocol_version)); + } + + Stage::AwaitLoginStart + } + }; + + Ok(()) +} + +fn handle_request(ih: &mut InitialHandler, packet: &Request) -> Result<(), Error> { + check_stage(ih, Stage::AwaitRequest, packet.ty())?; + + // Send response packet + let json = json!({ + "version": { + "name": SERVER_VERSION, + "protocol": PROTOCOL_VERSION, + }, + "players": { + "max": ih.config.server.max_players, + "online": ih.player_count.load(Ordering::SeqCst), + }, + "description": { + "text": ih.config.server.motd, + } + }); + + let response = Response::new(json.to_string()); + send_packet(ih, response); + + ih.stage = Stage::AwaitPing; + + Ok(()) +} + +fn handle_ping(ih: &mut InitialHandler, packet: &Ping) -> Result<(), Error> { + check_stage(ih, Stage::AwaitPing, packet.ty())?; + + let pong = Pong::new(packet.payload); + send_packet(ih, pong); + + // After sending pong, we should disconnect. + ih.should_disconnect = true; + ih.stage = Stage::Finished; + + Ok(()) +} + +fn handle_login_start(ih: &mut InitialHandler, packet: &LoginStart) -> Result<(), Error> { + check_stage(ih, Stage::AwaitLoginStart, packet.ty())?; + + ih.username = Some(packet.username.clone()); + + // If in online mode, encryption needs to be enabled, + // and authentication needs to be performed. + // If not in online mode, the login sequence is + // already finished, so we can call `finish` after + // setting the player's info. + if ih.config.server.online_mode { + // Enable encryption + let encryption_request = EncryptionRequest::new( + "", // Server ID - always empty + ih.rsa.public_key_to_der().unwrap(), + ih.verify_token.to_vec(), + ); + send_packet(ih, encryption_request); + + ih.stage = Stage::AwaitEncryptionResponse; + } else { + // Finished - set info and join + ih.info = JoinResult { + username: ih.username.unwrap(), + uuid: Uuid::new_v4(), + props: vec![], + }; + finish(ih); + } + + Ok(()) +} + +/// Terminates the login process, sending Set Compression (if necessary) +/// and Login Success. +/// +/// Before calling this function, it is expected that: +/// * `info` is set to a valid value +/// * Encryption has been enabled, if necessary +/// * All other login processes have already run +fn finish(ih: &mut InitialHandler) { + assert!(ih.info.is_some()); + + // Enable compression if necessary + let compression_threshold = ih.config.io.compression_threshold; + if compression_threshold > 0 { + enable_compression(ih, compression_threshold); + } + + let info = ih.info.as_ref().unwrap(); + + // Send Login Success + let login_success = LoginSuccess::new( + info.uuid.to_hyphenated_ref().to_string(), + info.username.clone(), + ); + send_packet(ih, login_success); +} + +/// Enables compression, sending the Set Compression +/// packet. +fn enable_compression(ih: &mut InitialHandler, threshold: i32) { + ih.compression_threshold = Some(threshold); + send_packet(ih, SetCompression::new(threshold)); +} + +/// Checks that the initial handler stage matches +/// the expected stage, returning `Err` with a proper +/// error message if not. +fn check_stage(ih: &InitialHandler, expected: Stage, packet_ty: PacketType) -> Result<(), Error> { + if ih.stage != expected { + Err(Error::InvalidPacket(packet_ty, ih.stage)) + } else { + Ok(()) + } +} + +/// Disconnects the initial handler, sending +/// a disconnect packet containing the reason. +fn disconnect_login(ih: &mut IntitialHandler, reason: &str) { + ih.should_disconnect = true; + + let json = json!({ + "text": reason, + }) + .to_string(); + + let packet = DisconnectLogin::new(json); + send_packet(ih, packet); +} + +/// Adds a packet to the internal packet queue. +fn send_packet(ih: &mut InitialHandler, packet: P) { + ih.packets_to_send.push(Box::new(packet)); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Error { + InvalidPacket(PacketType, Stage), + InvalidProtocol(u32), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + Error::InvalidPacket(ty, stage) => write!( + f, + "Sent invalid packet {:?} at initial handler stage {:?}", + ty, stage + )?, + Error::InvalidProtocol(protocol) => write!( + f, + "Invalid protocol version {} - this server is on {}", + *protocol, PROTOCOL_VERSION + )?, + } + + Ok(()) + } +} + +/// The stage of an initial handler. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Stage { + AwaitHandshake, + AwaitRequest, + AwaitPing, + AwaitLoginStart, + AwaitEncryptionResponse, + Finished, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::PROTOCOL_VERSION; + use feather_core::network::cast_packet; + use feather_core::network::packet::implementation::{ + Handshake, HandshakeState, LoginSuccess, Ping, Pong, Request, Response, SetCompression, + }; + use feather_core::network::packet::PacketType; + use feather_core::network::packet::PacketType::{ConfirmTransactionClientbound, LoginStart}; + + #[test] + fn test_initial_handler_new() { + let mut ih = ih(); + + assert!(ih.packets_to_send().is_empty()); + assert(!ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert!(ih.should_enable_compression().is_some()); + } + + #[test] + fn test_packets_to_send_empty() { + let mut ih = ih(); + + // Fake some packets + ih.packets_to_send.push(Response::new(0)); + + let queue = ih.packets_to_send(); + assert_eq!(queue.len(), 1); + assert_eq!(queue.first().unwrap().ty(), PacketType::Response); + } + + #[test] + fn test_should_enable_encryption() { + let mut ih = ih(); + + assert!(ih.should_enable_encryption().is_none); + + let key = rand::random(); + ih.key = Some(key.clone()); + + assert_eq!(key, ih.should_enable_encryption().unwrap()); + } + + #[test] + fn test_should_disconnect() { + let mut ih = ih(); + + assert!(!ih.should_disconnect()); + ih.should_disconnect = true; + + assert!(ih.should_disconnect()); + } + + #[test] + fn test_status_ping() { + let player_count = 24; + let mut ih = ih_with_player_count(player_count); + + assert!(!ih.should_disconnect()); + + let handshake = Handshake::new( + PROTOCOL_VERSION, + "", // Unused - server address + 25565, + HandshakeState::Status, + ); + ih.handle_packet(Box::new(handshake)); + + // Confirm that no packets were sent and the player wasn't disconnected + assert!(ih.packets_to_send().is_empty()); + assert!(!ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert!(ih.should_enable_compression().is_none()); + + let request = Request::new(); + ih.handle_packet(Box::new(request)); + + assert!(!ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert!(ih.should_enable_compression().is_none()); + + // Confirm that correct response was received + let sent_packets = ih.packets_to_send(); + assert_eq!(sent_packets.len(), 1); + + let _response = sent_packets.first().unwrap(); + assert_eq!(_request.ty(), PacketType::Response); + + let response = cast_packet::(&_response); + let _: serde_json::Value = serde_json::from_str(&response.json_response).unwrap(); + + // Send ping + let payload = 39842; + let ping = Ping::new(payload); + ih.handle_packet(Box::new(ping)); + + assert!(ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert!(ih.should_enable_compression().is_none()); + + let sent_packets = ih.packets_to_send(); + assert_eq!(sent_packets.len(), 1); + let _pong = sent_packets.first().unwrap(); + + assert_eq!(_pong.ty(), PacketType::Pong); + let pong = cast_packet::(&_pong); + assert_eq!(pong.payload, payload); + } + + #[test] + fn test_login_sequence() { + let mut config = Config::default(); + config.server.online_mode = false; + let mut ih = ih_with_config(config.clone()); + + let handshake = Handshake::new( + PROTOCOL_VERSION, + "", // Unused - server address + 25565, + HandshakeState::Login, + ); + ih.handle_packet(Box::new(handshake)); + + let sent_packets = ih.packets_to_send(); + assert!(sent_packets.is_empty()); + assert!(!ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert!(ih.should_enable_compression().is_none()); + + let username = "test"; + let login_start = LoginStart::new(username.to_string()); + ih.handle_packet(Box::new(login_start)); + + assert!(!ih.should_disconnect()); + assert!(ih.should_enable_encryption().is_none()); + assert_eq!( + ih.should_enable_compression(), + Some(config.io.compression_threshold) + ); + + let sent_packets = ih.packets_to_send(); + assert_eq!(sent_packets.len(), 2); + + let _set_compression = sent_packets.first().unwrap(); + assert_eq!(_set_compression.ty(), PacketType::SetCompression); + + let set_compression = cast_packet::(&_set_compression); + assert_eq!(set_compression.threshold, config.io.compression_threshold); + + let _login_success = sent_packets.first().unwrap(); + assert_eq!(_login_success.ty(), PacketType::LoginSuccess); + + let login_success = cast_packet::(&_login_success); + assert_eq!(login_success.username, username.to_string()); + assert!(ih.should_join().is_some()); + } + + fn ih() -> InitialHandler { + InitialHandler::new(Arc::new(Config::default()), Arc::new(PlayerCount(0))) + } + + fn ih_with_player_count(count: usize) -> InitialHandler { + InitialHandler::new(Arc::new(Config::default()), Arc::new(PlayerCount(count))) + } + + fn ih_with_config(config: Config) -> InitialHandler { + InitialHandler::new(Arc::new(config), Arc::new(PlayerCount(0))) + } +} diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 87d9bf447..49d20aa8f 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -2,7 +2,9 @@ use feather_core::network::packet::Packet; use mio_extras::channel::{channel, Receiver, Sender}; use std::net::SocketAddr; use std::thread; +use uuid::Uuid; +mod initialhandler; mod listener; mod worker; @@ -14,9 +16,6 @@ pub enum ServerToWorkerMessage { NotifyPacketReceived(Box), NotifyDisconnect, Disconnect, - - EnableCompression(usize), - EnableEncryption([u8; 16]), } pub enum ServerToListenerMessage { @@ -32,6 +31,9 @@ pub enum ListenerToWorkerMessage { pub struct NewClientInfo { pub ip: SocketAddr, + pub username: String, + pub profile: Vec, + pub uuid: Uuid, pub sender: Sender, pub receiver: Receiver, diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 5a8a8fa26..2427e246f 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -1,3 +1,4 @@ +use super::initialhandler as ih; use super::*; use bytes::BufMut; use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; @@ -186,18 +187,6 @@ fn read_from_server(worker: &mut Worker, token: Token) { break; } ServerToWorkerMessage::SendPacket(packet) => send_packet(worker, client_id, packet), - ServerToWorkerMessage::EnableCompression(threshold) => worker - .clients - .get_mut(&client_id) - .unwrap() - .manager - .enable_compression(threshold), - ServerToWorkerMessage::EnableEncryption(key) => worker - .clients - .get_mut(&client_id) - .unwrap() - .manager - .enable_encryption(key), _ => panic!("Invalid message received from server thread"), } } diff --git a/server/src/main.rs b/server/src/main.rs index dbabcebb7..248cc509e 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -14,7 +14,6 @@ pub mod chunkclient; pub mod chunkworker; pub mod config; pub mod entity; -pub mod initialhandler; pub mod io; pub mod network; pub mod player; @@ -36,7 +35,7 @@ pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; pub const TICK_TIME: u64 = 1000 / TPS; #[derive(Default, Debug, Clone, Copy)] -pub struct PlayerCount(u32); +pub struct PlayerCount(usize); #[derive(Default, Debug, Clone, Copy)] pub struct TickCount(u64); @@ -108,11 +107,6 @@ fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Di let mut dispatcher = DispatcherBuilder::new() .with(chunkclient::ChunkLoadSystem, "chunk_load", &[]) .with(network::NetworkSystem, "network", &[]) - .with( - initialhandler::InitialHandlerSystem, - "initial_handler", - &["network", "chunk_load"], - ) .with( worldupdate::PlayerDiggingSystem, "player_digging", diff --git a/server/src/network.rs b/server/src/network.rs index bb0b21a74..75751b333 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,5 +1,4 @@ use crate::entity::{EntityComponent, PlayerComponent}; -use crate::initialhandler::InitialHandlerComponent; use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; use crate::prelude::*; use crate::TickCount; @@ -173,18 +172,6 @@ impl<'a> System<'a> for NetworkSystem { } } -pub fn enable_compression_for_player(net: &NetworkComponent, threshold: usize) { - let _ = net - .sender - .send(ServerToWorkerMessage::EnableCompression(threshold)); -} - -pub fn enable_encryption_for_player(net: &NetworkComponent, key: [u8; 16]) { - let _ = net - .sender - .send(ServerToWorkerMessage::EnableEncryption(key)); -} - /// Returns the player info and spawn player packets /// for the given player. pub fn get_player_initialization_packets( diff --git a/server/src/player.rs b/server/src/player.rs index e26308409..f1afb1a3d 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -137,8 +137,7 @@ impl<'a> System<'a> for ChunkSendSystem { // Chunk has been loaded - send it to_remove.push(*pos); - let packet = ChunkData::new(chunk.clone()); - send_packet_to_player(net, packet); + send_chunk_data(chunk, net); } } From 9652e9a909fe0a52f1ef25b31f4617585fcfca63 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Thu, 25 Jul 2019 21:30:57 -0600 Subject: [PATCH 19/30] Implement handle_encryption_response() --- server/src/io/initialhandler.rs | 60 ++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index 723a52fe5..f3aecd4ef 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -25,7 +25,7 @@ use feather_core::network::packet::implementation::{ use feather_core::network::packet::PacketType::DisconnectLogin; use feather_core::network::packet::{Packet, PacketType}; use openssl::pkey::Private; -use openssl::rsa::Rsa; +use openssl::rsa::{Padding, Rsa}; use std::sync::atomic::Ordering; use std::sync::Arc; use uuid::Uuid; @@ -38,6 +38,8 @@ type VerifyToken = [u8; 4]; /// The number of bits used for the RSA key. const RSA_KEY_BITS: u32 = 1024; +/// The number of bytes in the shared secret +const SHARED_SECRET_LEN: usize = 128 / 8; /// The type returned for when a player has completed the login process. pub struct JoinResult { @@ -123,8 +125,8 @@ impl InitialHandler { /// Notifies this initial handler of a packet /// received from the client. After calling this - /// function, `packets_to_send`, `should_disconnect`, - /// `should_enable_compression`, `should_enable_encryption`, + /// function, `should_enable_encryption`, `packets_to_send`, `should_disconnect`, + /// `should_enable_compression`, /// and `should_join`, in that order, /// should be called to handle those various events. pub fn handle_packet(&mut self, packet: Box) { @@ -305,17 +307,59 @@ fn handle_login_start(ih: &mut InitialHandler, packet: &LoginStart) -> Result<() ih.stage = Stage::AwaitEncryptionResponse; } else { // Finished - set info and join - ih.info = JoinResult { + ih.info = Some(JoinResult { username: ih.username.unwrap(), uuid: Uuid::new_v4(), props: vec![], - }; + }); finish(ih); } Ok(()) } +fn handle_encryption_response( + ih: &mut InitialHandler, + packet: &EncryptionResponse, +) -> Result<(), Error> { + check_stage(ih, Stage::AwaitEncryptionResponse, packet.ty())?; + + // Decrypt verify token + shared secret + let shared_secret = decrypt_using_rsa(&packet.secret, &ih.rsa)?; + if shared_secret.len() != SHARED_SECRET_LEN { + return Err(Error::BadSecretLength); + } + + let verify_token = decrypt_using_rsa(&packet.verify_token, &ih.rsa)?; + if verify_token.len() != ih.verify_token.len() { + return Err(Error::VerifyTokenMismatch); + } + + // Check that verify token matches + if verify_token.as_slice() != &ih.verify_token { + return Err(Error::VerifyTokenMismatch); + } + + // Enable encryption + ih.key = Some(shared_secret[..SHARED_SECRET_LEN].clone()); + + finish(ih); + + Ok(()) +} + +fn decrypt_using_rsa(data: &[u8], key: &Rsa) -> Result, Error> { + let mut buf = vec![0u8; key.size() as usize]; + + let len = key + .private_decrypt(data, &mut buf, Padding::PKCS1) + .map_err(|e| Error::BadEncryption)?; + + buf.truncate(len); + + Ok(buf) +} + /// Terminates the login process, sending Set Compression (if necessary) /// and Login Success. /// @@ -383,6 +427,9 @@ fn send_packet(ih: &mut InitialHandler, packet: P) { enum Error { InvalidPacket(PacketType, Stage), InvalidProtocol(u32), + BadEncryption, + VerifyTokenMismatch, + BadSecretLength, } impl std::fmt::Display for Error { @@ -398,6 +445,9 @@ impl std::fmt::Display for Error { "Invalid protocol version {} - this server is on {}", *protocol, PROTOCOL_VERSION )?, + Error::BadEncryption => write!(f, "Failed to decrypt value")?, + Error::VerifyTokenMismatch => write!(f, "Verify token does not match")?, + Error::BadSecretLength => write!(f, "Invalid shared secret length")?, } Ok(()) From 13f458156e90dc8d17eb11bd5be9aa6d5bc7f5d4 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 10:10:49 -0600 Subject: [PATCH 20/30] Complete rewrite of initial handler --- Cargo.lock | 1 + core/src/network/packet/implementation.rs | 2 +- server/Cargo.toml | 1 + server/src/chunkclient.rs | 22 ++- server/src/entity.rs | 16 --- server/src/io/initialhandler.rs | 55 ++++++-- server/src/io/mod.rs | 16 ++- server/src/io/worker.rs | 140 ++++++++++++++----- server/src/joinhandler.rs | 156 ++++++++++++++++++++++ server/src/main.rs | 41 ++++-- server/src/network.rs | 18 ++- server/src/player.rs | 77 ++++++++--- 12 files changed, 444 insertions(+), 101 deletions(-) create mode 100644 server/src/joinhandler.rs diff --git a/Cargo.lock b/Cargo.lock index f8f12d844..3486f5a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,6 +466,7 @@ dependencies = [ "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "specs 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/src/network/packet/implementation.rs b/core/src/network/packet/implementation.rs index 1e9ab34f3..417d4c1fe 100644 --- a/core/src/network/packet/implementation.rs +++ b/core/src/network/packet/implementation.rs @@ -565,7 +565,7 @@ impl Packet for EncryptionRequest { fn write_to(&self, mut buf: &mut ByteBuf) { buf.write_string(self.server_id.as_str()); - buf.write_var_int(self.public_key.len()) as i32; + buf.write_var_int(self.public_key.len() as i32); buf.write(&self.public_key); buf.write_var_int(self.verify_token.len() as i32); diff --git a/server/Cargo.toml b/server/Cargo.toml index 36de94e77..9d28b2c1d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -27,6 +27,7 @@ multimap = "0.5.0" rnbt = { git = "https://github.com/caelunshun/rnbt", version = "0.1.0" } specs = "0.15.0" rayon = "1.1.0" +shrev = "1.1.1" [dev-dependencies] mockers = "0.21.0" diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index 8867f36c7..e2d510b4c 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -3,6 +3,7 @@ use crate::chunkworker; use crossbeam::channel::{Receiver, Sender}; use feather_core::world::{ChunkMap, ChunkPosition}; +use shrev::EventChannel; use specs::{Read, System, World, Write}; pub struct ChunkWorkerHandle { @@ -17,24 +18,39 @@ impl Default for ChunkWorkerHandle { } } +/// Event which is triggered when a chunk is loaded. +#[derive(Debug, Clone, Copy)] +pub struct ChunkLoadEvent { + pub pos: ChunkPosition, +} + /// System for receiving loaded chunks from the chunk worker thread. pub struct ChunkLoadSystem; impl<'a> System<'a> for ChunkLoadSystem { - type SystemData = (Write<'a, ChunkMap>, Read<'a, ChunkWorkerHandle>); + type SystemData = ( + Write<'a, ChunkMap>, + Write<'a, EventChannel>, + Read<'a, ChunkWorkerHandle>, + ); fn run(&mut self, data: Self::SystemData) { - let (mut chunk_map, handle) = data; + let (mut chunk_map, mut events, handle) = data; while let Ok((pos, result)) = handle.receiver.try_recv() { match result { Ok(chunk) => { chunk_map.set_chunk_at(pos, chunk); + + // Trigger event + let event = ChunkLoadEvent { pos }; + events.single_write(event); + trace!("Loaded chunk at {:?}", pos); } Err(err) => { // TODO generate chunk if it didn't exist - warn!("Failed to load chunk at {:?}: {:?}", pos, err); + warn!("Failed to load chunk at {:?}: {}", pos, err); } } } diff --git a/server/src/entity.rs b/server/src/entity.rs index f1f6d0fa8..a8688c728 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -86,22 +86,6 @@ pub fn broadcast_entity_movement( } } -/// Confirms that a player is past the login -/// sequence and has joined the server, disconnecting -/// them and returning `false` if they have not. -pub fn check_player_joined( - player: Entity, - pcomps: &ReadStorage, - lazy: &LazyUpdate, -) -> bool { - if !pcomps.contains(player) { - disconnect_player(player, "You have not yet joined!", lazy); - return false; - } - - true -} - /// Calculates the relative move fields /// as used in the Entity Relative Move packets. pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index f3aecd4ef..2d6ce83fc 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -19,13 +19,13 @@ use crate::io::NewClientInfo; use crate::{PlayerCount, PROTOCOL_VERSION, SERVER_VERSION}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ - EncryptionRequest, EncryptionResponse, Handshake, HandshakeState, LoginStart, LoginSuccess, - Ping, Pong, Request, Response, + DisconnectLogin, EncryptionRequest, EncryptionResponse, Handshake, HandshakeState, LoginStart, + LoginSuccess, Ping, Pong, Request, Response, SetCompression, }; -use feather_core::network::packet::PacketType::DisconnectLogin; use feather_core::network::packet::{Packet, PacketType}; use openssl::pkey::Private; use openssl::rsa::{Padding, Rsa}; +use std::str::FromStr; use std::sync::atomic::Ordering; use std::sync::Arc; use uuid::Uuid; @@ -42,6 +42,7 @@ const RSA_KEY_BITS: u32 = 1024; const SHARED_SECRET_LEN: usize = 128 / 8; /// The type returned for when a player has completed the login process. +#[derive(Clone, Debug)] pub struct JoinResult { pub username: String, pub uuid: Uuid, @@ -137,6 +138,11 @@ impl InitialHandler { if let Err(e) = _handle_packet(self, packet) { // Disconnect disconnect_login(self, &format!("{}", e)); + info!( + "Player {} disconnected: {}", + self.username.as_ref().unwrap_or(&"unknown".to_string()), + e + ); } } @@ -203,7 +209,7 @@ impl InitialHandler { /// If `Some` is returned, no further calls to `handle_packet` /// should be made. pub fn should_join(&self) -> Option { - self.info + self.info.clone() } } @@ -257,7 +263,7 @@ fn handle_request(ih: &mut InitialHandler, packet: &Request) -> Result<(), Error }, "players": { "max": ih.config.server.max_players, - "online": ih.player_count.load(Ordering::SeqCst), + "online": ih.player_count.0.load(Ordering::SeqCst), }, "description": { "text": ih.config.server.motd, @@ -298,7 +304,7 @@ fn handle_login_start(ih: &mut InitialHandler, packet: &LoginStart) -> Result<() if ih.config.server.online_mode { // Enable encryption let encryption_request = EncryptionRequest::new( - "", // Server ID - always empty + "".to_string(), // Server ID - always empty ih.rsa.public_key_to_der().unwrap(), ih.verify_token.to_vec(), ); @@ -308,7 +314,7 @@ fn handle_login_start(ih: &mut InitialHandler, packet: &LoginStart) -> Result<() } else { // Finished - set info and join ih.info = Some(JoinResult { - username: ih.username.unwrap(), + username: ih.username.clone().unwrap(), uuid: Uuid::new_v4(), props: vec![], }); @@ -341,7 +347,34 @@ fn handle_encryption_response( } // Enable encryption - ih.key = Some(shared_secret[..SHARED_SECRET_LEN].clone()); + let mut key = [0u8; SHARED_SECRET_LEN]; + for (i, x) in shared_secret[..SHARED_SECRET_LEN].into_iter().enumerate() { + key[i] = *x; + } + + ih.key = Some(key); + + // Perform authentication + let auth_result = mojang_api::server_auth( + ih.username.as_ref().unwrap(), + &mojang_api::server_hash( + "", + ih.key.clone().unwrap(), + ih.rsa.public_key_to_der().unwrap().as_slice(), + ), + ); + + match auth_result { + Ok(auth) => { + let info = JoinResult { + username: auth.name, + uuid: Uuid::from_str(&auth.id).unwrap(), + props: auth.properties, + }; + ih.info = Some(info); + } + Err(_) => return Err(Error::AuthenticationFailed), + } finish(ih); @@ -406,7 +439,7 @@ fn check_stage(ih: &InitialHandler, expected: Stage, packet_ty: PacketType) -> R /// Disconnects the initial handler, sending /// a disconnect packet containing the reason. -fn disconnect_login(ih: &mut IntitialHandler, reason: &str) { +fn disconnect_login(ih: &mut InitialHandler, reason: &str) { ih.should_disconnect = true; let json = json!({ @@ -419,7 +452,7 @@ fn disconnect_login(ih: &mut IntitialHandler, reason: &str) { } /// Adds a packet to the internal packet queue. -fn send_packet(ih: &mut InitialHandler, packet: P) { +fn send_packet(ih: &mut InitialHandler, packet: P) { ih.packets_to_send.push(Box::new(packet)); } @@ -430,6 +463,7 @@ enum Error { BadEncryption, VerifyTokenMismatch, BadSecretLength, + AuthenticationFailed, } impl std::fmt::Display for Error { @@ -448,6 +482,7 @@ impl std::fmt::Display for Error { Error::BadEncryption => write!(f, "Failed to decrypt value")?, Error::VerifyTokenMismatch => write!(f, "Verify token does not match")?, Error::BadSecretLength => write!(f, "Invalid shared secret length")?, + Error::AuthenticationFailed => write!(f, "Authentication failed")?, } Ok(()) diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 49d20aa8f..0700a6c0c 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -1,6 +1,9 @@ +use crate::config::Config; +use crate::PlayerCount; use feather_core::network::packet::Packet; use mio_extras::channel::{channel, Receiver, Sender}; use std::net::SocketAddr; +use std::sync::Arc; use std::thread; use uuid::Uuid; @@ -47,7 +50,12 @@ pub struct NetworkIoManager { impl NetworkIoManager { /// Starts a new IO event loop with the specified number /// of worker threads. - pub fn start(addr: SocketAddr, num_worker_threads: u16) -> Self { + pub fn start( + addr: SocketAddr, + num_worker_threads: u16, + config: Arc, + player_count: Arc, + ) -> Self { info!( "Starting IO event loop on {} with {} worker threads", addr, num_worker_threads @@ -57,8 +65,10 @@ impl NetworkIoManager { for _ in 0..num_worker_threads { let (send1, recv1) = channel(); let (send2, recv2) = channel(); + let player_count = Arc::clone(&player_count); + let config = Arc::clone(&config); - thread::spawn(move || worker::start(recv1, send2)); + thread::spawn(move || worker::start(recv1, send2, config, player_count)); workers.push((send1, recv2)); } @@ -83,6 +93,6 @@ impl NetworkIoManager { impl Default for NetworkIoManager { fn default() -> Self { - Self::start("127.0.0.1:25565".parse().unwrap(), 8) + panic!("Nope, don't call default() on the IO manager. That won't work."); } } diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 2427e246f..804d7bd87 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -1,5 +1,8 @@ use super::initialhandler as ih; use super::*; +use crate::config::Config; +use crate::io::initialhandler::InitialHandler; +use crate::PlayerCount; use bytes::BufMut; use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; use feather_core::network::packet::PacketDirection; @@ -9,6 +12,7 @@ use mio::Event; use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; use std::io::Read; use std::io::Write; +use std::sync::Arc; // The token used to listen on the channel receiving messages from the listener thread const LISTENER_TOKEN: Token = Token(0); @@ -23,6 +27,9 @@ struct Worker { clients: HashMap, pending_disconnects: Vec, + + config: Arc, + player_count: Arc, } struct ClientHandle { @@ -31,19 +38,26 @@ struct ClientHandle { write_buffer: Option, - receiver: Receiver, - sender: Sender, + receiver: Option>, + sender: Option>, stream_token: Token, server_to_worker_token: Token, manager: ConnectionIOManager, + + initial_handler: Option, } /// Starts an IO worker on the current thread, /// blocking indefinitely until a `ShutDown` message /// is received from the listener. -pub fn start(receiver: Receiver, sender: Sender) { +pub fn start( + receiver: Receiver, + sender: Sender, + config: Arc, + player_count: Arc, +) { trace!("Starting IO worker thread"); let poll = Poll::new().unwrap(); @@ -57,6 +71,8 @@ pub fn start(receiver: Receiver, sender: Sender Result<(), ()> { fn handle_packet(worker: &mut Worker, client_id: Client, packet: Box) { let client = worker.clients.get_mut(&client_id).unwrap(); - let msg = ServerToWorkerMessage::NotifyPacketReceived(packet); - client.sender.send(msg).unwrap(); + let mut packets_to_send = vec![]; + let mut should_disconnect = false; + let mut encryption_key = None; + let mut compression_threshold = None; + let mut should_join = None; + + if let Some(ih) = client.initial_handler.as_mut() { + // Forward packet to the initial handler. + ih.handle_packet(packet); + + encryption_key = ih.should_enable_encryption(); + packets_to_send = ih.packets_to_send(); + should_disconnect = ih.should_disconnect(); + compression_threshold = ih.should_enable_compression(); + should_join = ih.should_join(); + } else { + // Forward packet to the server. + let msg = ServerToWorkerMessage::NotifyPacketReceived(packet); + client.sender.as_ref().unwrap().send(msg).unwrap(); + } + + if let Some(key) = encryption_key { + client.manager.enable_encryption(key); + } + + packets_to_send + .into_iter() + .for_each(|packet| send_packet(worker, client_id, packet)); + + let client = worker.clients.get_mut(&client_id).unwrap(); + + if should_disconnect { + disconnect_client(worker, client_id); + return; + } + + if let Some(threshold) = compression_threshold { + client.manager.enable_compression(threshold as usize); + } + + if let Some(info) = should_join { + let (send1, recv1) = channel(); + let (send2, recv2) = channel(); + + let player_info = NewClientInfo { + ip: client.addr.clone(), + username: info.username, + profile: info.props, + uuid: info.uuid, + sender: send1, + receiver: recv2, + }; + + worker + .sender + .send(ListenerToWorkerMessage::NewClient(player_info)) + .unwrap(); + + client.initial_handler = None; + + client.sender = Some(send2); + client.receiver = Some(recv1); + + worker + .poll + .register( + client.receiver.as_ref().unwrap(), + client.server_to_worker_token, + Ready::readable(), + PollOpt::edge(), + ) + .unwrap(); + } } fn get_stream_token(client_id: Client) -> Token { diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs new file mode 100644 index 000000000..13d688461 --- /dev/null +++ b/server/src/joinhandler.rs @@ -0,0 +1,156 @@ +//! The join handler, in contrast to the initial handler, +//! takes over after the login sequence has completed. +//! It's responsible for asyncrhonously loading the player's +//! data (inventory, chunks, etc.) and then sending the necessary +//! packets to join the player. After completion, the component is +//! removed. + +use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; +use crate::config::Config; +use crate::network::NetworkComponent; +use crate::player::ChunkPendingComponent; +use crate::PlayerCount; +use feather_core::network::packet::implementation::{ + JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, +}; +use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; +use feather_core::{Difficulty, Dimension, Gamemode}; +use shrev::EventChannel; +use specs::{ + Component, Entities, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, ReaderId, System, + World, WriteStorage, +}; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +/// For now, we use a fixed spawn position. +/// In the future, the spawn position should +/// be loaded asynchronously from the world save. +const SPAWN_POSITION: Position = Position { + x: 0.0, + y: 64.0, + z: 0.0, + pitch: 0.0, + yaw: 0.0, +}; + +/// See `SPAWN_POSITION` +const COMPASS_SPAWN_POSITION: BlockPosition = BlockPosition { x: 0, y: 64, z: 0 }; + +pub struct JoinHandlerComponent { + stage: Stage, +} + +impl JoinHandlerComponent { + pub fn new() -> Self { + Self { + stage: Stage::Initial, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Stage { + Initial, + AwaitChunkSends, +} + +impl Component for JoinHandlerComponent { + type Storage = HashMapStorage; +} + +/// System for join handling. +pub struct JoinHandlerSystem; + +impl<'a> System<'a> for JoinHandlerSystem { + type SystemData = ( + WriteStorage<'a, JoinHandlerComponent>, + ReadStorage<'a, NetworkComponent>, + ReadStorage<'a, ChunkPendingComponent>, + Read<'a, ChunkWorkerHandle>, + Entities<'a>, + Read<'a, LazyUpdate>, + Read<'a, Arc>, + Read<'a, Arc>, + Read<'a, ChunkMap>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut joincomps, + netcomps, + pending_chunks, + worker_handle, + entities, + lazy, + config, + player_count, + chunk_map, + ) = data; + + let mut to_remove = vec![]; + + for (player, net, join_handler) in (&entities, &netcomps, &mut joincomps).join() { + match join_handler.stage { + Stage::Initial => { + // Send Join Game, then queue chunks for loading + sending. + let join_game = JoinGame::new( + player.id() as i32, + Gamemode::Creative.get_id(), + Dimension::Overwold.get_id(), + Difficulty::Medium.get_id(), + 0, // Max players - not used + "default".to_string(), // Level type + false, // Reduced debug info + ); + crate::network::send_packet_to_player(net, join_game); + + // Queue chunks + let view_distance = config.server.view_distance as i32; + for x in -view_distance..=view_distance { + for y in -view_distance..=view_distance { + let pos = ChunkPosition::new(x, y); + crate::player::send_chunk_to_player( + pos, + net, + player, + &chunk_map, + &worker_handle, + &lazy, + ); + } + } + + // Increment player count + player_count.0.fetch_add(1, Ordering::SeqCst); + + join_handler.stage = Stage::AwaitChunkSends; + } + Stage::AwaitChunkSends => { + // If 0 chunks have yet to be sent, join the player by sending spawn position. + // See https://wiki.vg/Protocol_FAQ + let spawn_position = SpawnPosition::new(COMPASS_SPAWN_POSITION); + crate::network::send_packet_to_player(net, spawn_position); + + let position_and_look = PlayerPositionAndLookClientbound::new( + SPAWN_POSITION.x, + SPAWN_POSITION.y, + SPAWN_POSITION.z, + SPAWN_POSITION.yaw, + SPAWN_POSITION.pitch, + 0, // Flags - unused by us + 0, // Teleport ID - unused by us + ); + crate::network::send_packet_to_player(net, position_and_look); + + // We're finished here. + to_remove.push(player); + } + } + } + + to_remove.into_iter().for_each(|player| { + joincomps.remove(player); + }); + } +} diff --git a/server/src/main.rs b/server/src/main.rs index 248cc509e..5904c9263 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -15,6 +15,7 @@ pub mod chunkworker; pub mod config; pub mod entity; pub mod io; +pub mod joinhandler; pub mod network; pub mod player; pub mod prelude; @@ -27,6 +28,8 @@ use crate::network::send_packet_to_player; use feather_core::network::packet::implementation::DisconnectPlay; use prelude::*; use specs::{Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, WorldExt}; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub const TPS: u64 = 20; @@ -34,22 +37,24 @@ pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; pub const TICK_TIME: u64 = 1000 / TPS; -#[derive(Default, Debug, Clone, Copy)] -pub struct PlayerCount(usize); -#[derive(Default, Debug, Clone, Copy)] +#[derive(Default, Debug)] +pub struct PlayerCount(AtomicUsize); +#[derive(Default, Debug)] pub struct TickCount(u64); fn main() { - let config = config::load_from_file("feather.toml") - .expect("Failed to load configuration. Please ensure that the file feather.toml exists and is correct."); + let config = Arc::new(config::load_from_file("feather.toml") + .expect("Failed to load configuration. Please ensure that the file feather.toml exists and is correct.")); init_log(&config); info!("Starting Feather; please wait..."); - let io_manager = init_io_manager(&config); + let player_count = Arc::new(PlayerCount(AtomicUsize::new(0))); - let (mut world, mut dispatcher) = init_world(config, io_manager); + let io_manager = init_io_manager(Arc::clone(&config), Arc::clone(&player_count)); + + let (mut world, mut dispatcher) = init_world(config, player_count, io_manager); info!("Initialized world"); @@ -89,18 +94,25 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { } /// Starts the IO threads. -fn init_io_manager(config: &Config) -> io::NetworkIoManager { +fn init_io_manager(config: Arc, player_count: Arc) -> io::NetworkIoManager { let ioman = io::NetworkIoManager::start( format!("127.0.0.1:{}", config.server.port).parse().unwrap(), config.io.io_worker_threads, + config, + player_count, ); ioman } /// Initializes the Specs world. -fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Dispatcher<'a, 'b>) { +fn init_world<'a, 'b>( + config: Arc, + player_count: Arc, + ioman: io::NetworkIoManager, +) -> (World, Dispatcher<'a, 'b>) { let mut world = World::new(); world.insert(config); + world.insert(player_count); world.insert(ioman); world.insert(TickCount::default()); @@ -117,7 +129,16 @@ fn init_world<'a, 'b>(config: Config, ioman: io::NetworkIoManager) -> (World, Di "player_movement", &["network"], ) - .with(player::ChunkSendSystem, "chunk_send", &["chunk_load"]) + .with( + player::ChunkSendSystem::new(), + "chunk_send", + &["chunk_load"], + ) + .with( + joinhandler::JoinHandlerSystem, + "join)handler", + &["chunk_send"], + ) .build(); dispatcher.setup(&mut world); diff --git a/server/src/network.rs b/server/src/network.rs index 75751b333..9714d8067 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,5 +1,6 @@ use crate::entity::{EntityComponent, PlayerComponent}; use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; +use crate::joinhandler::JoinHandlerComponent; use crate::prelude::*; use crate::TickCount; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; @@ -7,7 +8,7 @@ use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; use specs::{ Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, - Write, WriteStorage, + WorldExt, Write, WriteStorage, }; use std::sync::Mutex; @@ -111,7 +112,6 @@ pub struct NetworkSystem; impl<'a> System<'a> for NetworkSystem { type SystemData = ( WriteStorage<'a, NetworkComponent>, - WriteStorage<'a, InitialHandlerComponent>, ReadStorage<'a, PlayerComponent>, Write<'a, PacketQueue>, Read<'a, NetworkIoManager>, @@ -121,8 +121,7 @@ impl<'a> System<'a> for NetworkSystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut netcomps, mut ihcomps, pcomps, packet_queue, ioman, entities, tick_count, _) = - data; + let (mut netcomps, pcomps, packet_queue, ioman, entities, tick_count, lazy) = data; // Poll for new connections while let Ok(msg) = ioman.receiver.try_recv() { match msg { @@ -135,9 +134,14 @@ impl<'a> System<'a> for NetworkSystem { let new_entity = entities.create(); netcomps.insert(new_entity, netcomp).unwrap(); - // Create initial handler - let ih = InitialHandlerComponent::new(); - ihcomps.insert(new_entity, ih).unwrap(); + // Create join handler + let join_handler = JoinHandlerComponent::new(); + lazy.exec_mut(move |world| { + world + .write_component::() + .insert(new_entity, join_handler) + .unwrap(); + }); } _ => panic!("Network system received invalid message from IO listener"), } diff --git a/server/src/player.rs b/server/src/player.rs index f1afb1a3d..673a436f8 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -2,7 +2,7 @@ //! relating to players, including player movement //! and inventory handling. -use crate::chunkclient::ChunkWorkerHandle; +use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; use feather_core::network::cast_packet; @@ -14,11 +14,13 @@ use feather_core::world::chunk::Chunk; use feather_core::world::{ChunkMap, ChunkPosition, Position}; use hashbrown::HashSet; use rayon::prelude::*; +use shrev::EventChannel; use specs::storage::BTreeStorage; use specs::{ - Component, Entities, Entity, LazyUpdate, ParJoin, Read, ReadStorage, System, WorldExt, - WriteStorage, + Component, Entities, Entity, LazyUpdate, ParJoin, Read, ReadStorage, ReaderId, System, World, + WorldExt, WriteStorage, }; +use std::ops::{Deref, DerefMut}; /// System for handling player movement /// packets. @@ -111,40 +113,75 @@ pub struct ChunkPendingComponent { pub pending: HashSet, } +impl Deref for ChunkPendingComponent { + type Target = HashSet; + + fn deref(&self) -> &Self::Target { + &self.pending + } +} + +impl DerefMut for ChunkPendingComponent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.pending + } +} + impl Component for ChunkPendingComponent { type Storage = BTreeStorage; } /// System for sending chunks to players once they're loaded. -pub struct ChunkSendSystem; +/// +/// This system listens to `ChunkLoadEvent`s. +pub struct ChunkSendSystem { + load_event_reader: Option>, +} + +impl ChunkSendSystem { + pub fn new() -> Self { + Self { + load_event_reader: None, + } + } +} impl<'a> System<'a> for ChunkSendSystem { type SystemData = ( WriteStorage<'a, ChunkPendingComponent>, ReadStorage<'a, NetworkComponent>, Read<'a, ChunkMap>, + Read<'a, EventChannel>, ); fn run(&mut self, data: Self::SystemData) { - let (mut pendings, netcomps, chunk_map) = data; - - (&mut pendings, &netcomps) - .par_join() - .for_each(|(pending, net)| { - let mut to_remove = vec![]; - for pos in &pending.pending { - if let Some(chunk) = chunk_map.chunk_at(*pos) { - // Chunk has been loaded - send it - to_remove.push(*pos); - + let (mut pendings, netcomps, chunk_map, load_events) = data; + + for event in load_events.read(&mut self.load_event_reader.as_mut().unwrap()) { + // TODO perhaps this is slightly inefficient? + (&netcomps, &mut pendings) + .par_join() + .for_each(|(net, pending)| { + if pending.contains(&event.pos) { + // It's safe to unwrap the chunk value now, + // because we know it's been loaded. + let chunk = chunk_map.chunk_at(event.pos).unwrap(); send_chunk_data(chunk, net); - } - } - to_remove.into_iter().for_each(|pos| { - pending.pending.remove(&pos); + pending.remove(&event.pos); + } }); - }); + } + } + + fn setup(&mut self, world: &mut World) { + use specs::SystemData; + Self::SystemData::setup(world); + self.load_event_reader = Some( + world + .fetch_mut::>() + .register_reader(), + ); } } From 6bc228bbad9940684ccf81a1c9d53808f5237dde Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 10:23:15 -0600 Subject: [PATCH 21/30] Get tests to compile and pass --- server/src/chunkclient.rs | 3 +++ server/src/config.rs | 14 ++++++------- server/src/io/initialhandler.rs | 35 ++++++++++++++++++++------------- server/src/main.rs | 7 ++++--- server/src/testframework.rs | 13 ++++++++---- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index e2d510b4c..d619db45f 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -101,10 +101,13 @@ mod tests { let pos = ChunkPosition::new(0, 0); send2.send((pos, Ok(Chunk::new(pos)))).unwrap(); + let event_channel = EventChannel::::new(); + let mut system = ChunkLoadSystem; let mut world = World::new(); world.insert(chunk_map); world.insert(handle); + world.insert(event_channel); system.run_now(&world); diff --git a/server/src/config.rs b/server/src/config.rs index a4d53af8f..3203fc6ca 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -1,6 +1,6 @@ use std::fs::read_to_string; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Config { pub io: IO, pub proxy: Proxy, @@ -38,16 +38,16 @@ impl Default for Config { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct IO { pub compression_threshold: i32, pub io_worker_threads: u16, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Proxy {} -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Server { pub online_mode: bool, pub motd: String, @@ -57,7 +57,7 @@ pub struct Server { pub port: u16, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Gameplay { pub monster_spawning: bool, pub animal_spawning: bool, @@ -65,7 +65,7 @@ pub struct Gameplay { pub nerf_spawner_mobs: bool, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Log { pub level: String, } @@ -83,7 +83,7 @@ pub fn load(input: String) -> Result { Ok(config) } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub enum ProxyMode { None, Bungee, diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index 2d6ce83fc..cebb0ae04 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -509,16 +509,16 @@ mod tests { Handshake, HandshakeState, LoginSuccess, Ping, Pong, Request, Response, SetCompression, }; use feather_core::network::packet::PacketType; - use feather_core::network::packet::PacketType::{ConfirmTransactionClientbound, LoginStart}; + use std::sync::atomic::AtomicUsize; #[test] fn test_initial_handler_new() { let mut ih = ih(); assert!(ih.packets_to_send().is_empty()); - assert(!ih.should_disconnect()); + assert!(!ih.should_disconnect()); assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_some()); + assert!(ih.should_enable_compression().is_none()); } #[test] @@ -526,7 +526,8 @@ mod tests { let mut ih = ih(); // Fake some packets - ih.packets_to_send.push(Response::new(0)); + ih.packets_to_send + .push(Box::new(Response::new("foo".to_string()))); let queue = ih.packets_to_send(); assert_eq!(queue.len(), 1); @@ -537,9 +538,9 @@ mod tests { fn test_should_enable_encryption() { let mut ih = ih(); - assert!(ih.should_enable_encryption().is_none); + assert!(ih.should_enable_encryption().is_none()); - let key = rand::random(); + let key: Key = rand::random(); ih.key = Some(key.clone()); assert_eq!(key, ih.should_enable_encryption().unwrap()); @@ -564,7 +565,7 @@ mod tests { let handshake = Handshake::new( PROTOCOL_VERSION, - "", // Unused - server address + "".to_string(), // Unused - server address 25565, HandshakeState::Status, ); @@ -588,7 +589,7 @@ mod tests { assert_eq!(sent_packets.len(), 1); let _response = sent_packets.first().unwrap(); - assert_eq!(_request.ty(), PacketType::Response); + assert_eq!(_response.ty(), PacketType::Response); let response = cast_packet::(&_response); let _: serde_json::Value = serde_json::from_str(&response.json_response).unwrap(); @@ -619,7 +620,7 @@ mod tests { let handshake = Handshake::new( PROTOCOL_VERSION, - "", // Unused - server address + "".to_string(), // Unused - server address 25565, HandshakeState::Login, ); @@ -642,10 +643,10 @@ mod tests { Some(config.io.compression_threshold) ); - let sent_packets = ih.packets_to_send(); + let mut sent_packets = ih.packets_to_send(); assert_eq!(sent_packets.len(), 2); - let _set_compression = sent_packets.first().unwrap(); + let _set_compression = sent_packets.remove(0); assert_eq!(_set_compression.ty(), PacketType::SetCompression); let set_compression = cast_packet::(&_set_compression); @@ -660,14 +661,20 @@ mod tests { } fn ih() -> InitialHandler { - InitialHandler::new(Arc::new(Config::default()), Arc::new(PlayerCount(0))) + InitialHandler::new( + Arc::new(Config::default()), + Arc::new(PlayerCount(AtomicUsize::new(0))), + ) } fn ih_with_player_count(count: usize) -> InitialHandler { - InitialHandler::new(Arc::new(Config::default()), Arc::new(PlayerCount(count))) + InitialHandler::new( + Arc::new(Config::default()), + Arc::new(PlayerCount(AtomicUsize::new(count))), + ) } fn ih_with_config(config: Config) -> InitialHandler { - InitialHandler::new(Arc::new(config), Arc::new(PlayerCount(0))) + InitialHandler::new(Arc::new(config), Arc::new(PlayerCount(AtomicUsize::new(0)))) } } diff --git a/server/src/main.rs b/server/src/main.rs index 5904c9263..af92de242 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -200,10 +200,11 @@ mod tests { #[test] fn test_init_world() { - let config = Default::default(); - let ioman = init_io_manager(&config); + let config = Arc::new(Config::default()); + let player_count = Arc::new(PlayerCount(AtomicUsize::new(0))); + let ioman = init_io_manager(Arc::clone(&config), Arc::clone(&player_count)); - let (world, mut dispatcher) = init_world(config, ioman); + let (world, mut dispatcher) = init_world(config, player_count, ioman); dispatcher.dispatch(&world); } } diff --git a/server/src/testframework.rs b/server/src/testframework.rs index 40b72ec5d..35ae5a04b 100644 --- a/server/src/testframework.rs +++ b/server/src/testframework.rs @@ -4,6 +4,7 @@ use crate::config::Config; use crate::entity::{EntityComponent, PlayerComponent}; use crate::io::ServerToWorkerMessage; use crate::network::{NetworkComponent, PacketQueue}; +use crate::PlayerCount; use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::Position; use feather_core::Gamemode; @@ -11,19 +12,23 @@ use mio_extras::channel::{channel, Receiver, Sender}; use rand::Rng; use specs::{Builder, Dispatcher, Entity, World, WorldExt}; use std::net::TcpListener; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; use uuid::Uuid; /// Initializes a Specs world and dispatcher /// using default configuration options and an /// available server port. pub fn init_world<'a, 'b>() -> (World, Dispatcher<'a, 'b>) { - let mut config: Config = Default::default(); - + let mut config = Config::default(); config.server.port = find_open_port().unwrap(); - let ioman = super::init_io_manager(&config); + let config = Arc::new(config); + + let player_count = Arc::new(PlayerCount(AtomicUsize::new(0))); + let ioman = super::init_io_manager(Arc::clone(&config), Arc::clone(&player_count)); - super::init_world(config, ioman) + super::init_world(config, player_count, ioman) } pub struct Player { From 5fdb17405e5529afcfdfa89438e30c2e8964bf1d Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 10:44:34 -0600 Subject: [PATCH 22/30] Fix panic on join caused by components not being initialized --- server/src/io/worker.rs | 17 +++++------ server/src/joinhandler.rs | 2 +- server/src/main.rs | 5 +-- server/src/network.rs | 32 ++++++++++++++++++-- server/src/player.rs | 64 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 105 insertions(+), 15 deletions(-) diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 804d7bd87..4ff2ad1b3 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -201,17 +201,16 @@ fn disconnect_client(worker: &mut Worker, client_id: Client) { return; } - worker - .poll - .deregister(client.receiver.as_ref().unwrap()) - .unwrap(); worker.poll.deregister(&client.stream).unwrap(); - let _ = client - .sender - .as_ref() - .unwrap() - .send(ServerToWorkerMessage::NotifyDisconnect); + if let Some(sender) = client.sender.as_ref() { + worker + .poll + .deregister(client.receiver.as_ref().unwrap()) + .unwrap(); + + let _ = sender.send(ServerToWorkerMessage::NotifyDisconnect); + } debug!("Disconnecting client {}", client_id.0); diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs index 13d688461..b57d3abec 100644 --- a/server/src/joinhandler.rs +++ b/server/src/joinhandler.rs @@ -26,7 +26,7 @@ use std::sync::Arc; /// For now, we use a fixed spawn position. /// In the future, the spawn position should /// be loaded asynchronously from the world save. -const SPAWN_POSITION: Position = Position { +pub const SPAWN_POSITION: Position = Position { x: 0.0, y: 64.0, z: 0.0, diff --git a/server/src/main.rs b/server/src/main.rs index af92de242..deef4db01 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -77,7 +77,7 @@ fn run_loop(world: &mut World, dispatcher: &mut Dispatcher) { // Sleep correct amount let end_time = current_time_in_millis(); - let elapsed = start_time - end_time; + let elapsed = end_time - start_time; if elapsed > TICK_TIME { continue; // Behind - start next tick immediately } @@ -136,9 +136,10 @@ fn init_world<'a, 'b>( ) .with( joinhandler::JoinHandlerSystem, - "join)handler", + "join_handler", &["chunk_send"], ) + .with(player::PlayerInitSystem::new(), "player_init", &["network"]) .build(); dispatcher.setup(&mut world); diff --git a/server/src/network.rs b/server/src/network.rs index 9714d8067..8bdfd37cd 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,11 +1,12 @@ use crate::entity::{EntityComponent, PlayerComponent}; -use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; +use crate::io::{NetworkIoManager, NewClientInfo, ServerToListenerMessage, ServerToWorkerMessage}; use crate::joinhandler::JoinHandlerComponent; use crate::prelude::*; use crate::TickCount; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; use mio_extras::channel::{Receiver, Sender}; +use shrev::EventChannel; use specs::{ Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, WorldExt, Write, WriteStorage, @@ -109,10 +110,19 @@ impl Component for NetworkComponent { /// other systems can handle them. pub struct NetworkSystem; +/// Event which is triggered when a player joins. +pub struct PlayerJoinEvent { + pub player: Entity, + pub username: String, + pub uuid: Uuid, + pub profile_properties: Vec, +} + impl<'a> System<'a> for NetworkSystem { type SystemData = ( WriteStorage<'a, NetworkComponent>, ReadStorage<'a, PlayerComponent>, + Write<'a, EventChannel>, Write<'a, PacketQueue>, Read<'a, NetworkIoManager>, Entities<'a>, @@ -121,7 +131,16 @@ impl<'a> System<'a> for NetworkSystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut netcomps, pcomps, packet_queue, ioman, entities, tick_count, lazy) = data; + let ( + mut netcomps, + pcomps, + mut join_events, + packet_queue, + ioman, + entities, + tick_count, + lazy, + ) = data; // Poll for new connections while let Ok(msg) = ioman.receiver.try_recv() { match msg { @@ -142,6 +161,15 @@ impl<'a> System<'a> for NetworkSystem { .insert(new_entity, join_handler) .unwrap(); }); + + // Queue event + let event = PlayerJoinEvent { + player: new_entity, + username: info.username.clone(), + uuid: info.uuid.clone(), + profile_properties: info.profile.clone(), + }; + join_events.single_write(event); } _ => panic!("Network system received invalid message from IO listener"), } diff --git a/server/src/player.rs b/server/src/player.rs index 673a436f8..052eb7178 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -4,7 +4,8 @@ use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; +use crate::joinhandler::SPAWN_POSITION; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerJoinEvent}; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, @@ -12,6 +13,7 @@ use feather_core::network::packet::implementation::{ use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::chunk::Chunk; use feather_core::world::{ChunkMap, ChunkPosition, Position}; +use feather_core::Gamemode; use hashbrown::HashSet; use rayon::prelude::*; use shrev::EventChannel; @@ -131,6 +133,66 @@ impl Component for ChunkPendingComponent { type Storage = BTreeStorage; } +/// System for initializing the necessary components +/// when a player joins. +pub struct PlayerInitSystem { + join_event_reader: Option>, +} + +impl PlayerInitSystem { + pub fn new() -> Self { + Self { + join_event_reader: None, + } + } +} + +impl<'a> System<'a> for PlayerInitSystem { + type SystemData = ( + Read<'a, EventChannel>, + WriteStorage<'a, PlayerComponent>, + WriteStorage<'a, EntityComponent>, + WriteStorage<'a, ChunkPendingComponent>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (events, mut player_comps, mut entity_comps, mut chunk_pending_comps) = data; + + // Run through events + for event in events.read(&mut self.join_event_reader.as_mut().unwrap()) { + let player_comp = PlayerComponent { + profile_properties: event.profile_properties.clone(), + gamemode: Gamemode::Creative, + }; + player_comps.insert(event.player, player_comp); + + let entity_comp = EntityComponent { + uuid: event.uuid.clone(), + display_name: event.username.clone(), + position: SPAWN_POSITION, + on_ground: true, + }; + entity_comps.insert(event.player, entity_comp); + + let chunk_pending_comp = ChunkPendingComponent { + pending: HashSet::new(), + }; + chunk_pending_comps.insert(event.player, chunk_pending_comp); + } + } + + fn setup(&mut self, world: &mut World) { + use specs::SystemData; + Self::SystemData::setup(world); + + self.join_event_reader = Some( + world + .fetch_mut::>() + .register_reader(), + ); + } +} + /// System for sending chunks to players once they're loaded. /// /// This system listens to `ChunkLoadEvent`s. From 7752e9e66b605c2f65e6952289124c53710d9145 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 14:48:21 -0600 Subject: [PATCH 23/30] Get intial handler to work completely --- server/config/feather.toml | 2 +- server/src/chunkclient.rs | 12 +- server/src/io/initialhandler.rs | 292 +++++++++++++------------------- server/src/io/worker.rs | 138 +++++++-------- server/src/joinhandler.rs | 27 +-- server/src/main.rs | 20 ++- server/src/network.rs | 19 ++- server/src/player.rs | 29 ++-- server/src/testframework.rs | 26 +-- server/src/worldupdate.rs | 19 ++- 10 files changed, 268 insertions(+), 316 deletions(-) diff --git a/server/config/feather.toml b/server/config/feather.toml index 4d575eefc..1b4a6fb48 100644 --- a/server/config/feather.toml +++ b/server/config/feather.toml @@ -29,4 +29,4 @@ nerf_spawner_mobs = false pvp_style = "classic" [log] -level = "debug" +level = "trace" diff --git a/server/src/chunkclient.rs b/server/src/chunkclient.rs index d619db45f..a523637ee 100644 --- a/server/src/chunkclient.rs +++ b/server/src/chunkclient.rs @@ -1,11 +1,13 @@ //! Module for interacting with the chunk worker thread //! from the server threads. -use crate::chunkworker; use crossbeam::channel::{Receiver, Sender}; -use feather_core::world::{ChunkMap, ChunkPosition}; use shrev::EventChannel; use specs::{Read, System, World, Write}; +use feather_core::world::{ChunkMap, ChunkPosition}; + +use crate::chunkworker; + pub struct ChunkWorkerHandle { sender: Sender, receiver: Receiver, @@ -83,10 +85,12 @@ pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { #[cfg(test)] mod tests { - use super::*; + use specs::{RunNow, World, WorldExt}; + use feather_core::world::chunk::Chunk; use feather_core::world::ChunkPosition; - use specs::{RunNow, World, WorldExt}; + + use super::*; #[test] fn test_chunk_system() { diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index cebb0ae04..9ad6e433d 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -14,21 +14,24 @@ //! speeding up the login process and making the latency calculation in //! the server list ping as low as possible. -use crate::config::Config; -use crate::io::NewClientInfo; -use crate::{PlayerCount, PROTOCOL_VERSION, SERVER_VERSION}; +use std::str::FromStr; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +use openssl::pkey::Private; +use openssl::rsa::{Padding, Rsa}; +use uuid::Uuid; + use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ DisconnectLogin, EncryptionRequest, EncryptionResponse, Handshake, HandshakeState, LoginStart, LoginSuccess, Ping, Pong, Request, Response, SetCompression, }; use feather_core::network::packet::{Packet, PacketType}; -use openssl::pkey::Private; -use openssl::rsa::{Padding, Rsa}; -use std::str::FromStr; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use uuid::Uuid; + +use crate::config::Config; +use crate::io::NewClientInfo; +use crate::{PlayerCount, PROTOCOL_VERSION, SERVER_VERSION}; /// The key used for symmetric encryption. pub type Key = [u8; 16]; @@ -41,6 +44,16 @@ const RSA_KEY_BITS: u32 = 1024; /// The number of bytes in the shared secret const SHARED_SECRET_LEN: usize = 128 / 8; +/// An action for the worker thread to execute +/// after `InitialHandler::handle_packet` is called. +pub enum Action { + EnableCompression(i32), + EnableEncryption(Key), + SendPacket(Box), + Disconnect, + JoinGame(JoinResult), +} + /// The type returned for when a player has completed the login process. #[derive(Clone, Debug)] pub struct JoinResult { @@ -57,18 +70,14 @@ pub struct JoinResult { /// login sequence or the server list ping. /// /// The initial handler is able to communicate with the worker -/// implementation by exposing a number of methods which are -/// used to poll for actions. For example, `packets_to_send` returns -/// a vector of packets that should be sent to the client, and `should_disconnect` -/// returns whether the client should be disconnected. +/// implementation by exposing the `actions_to_execute` method, +/// which returns a vector of actions for the worker to execute. +/// These may include, for example, enabling encryption or sending +/// a packet. pub struct InitialHandler { - /// A queue of packets to send to the client. - /// When `packets_to_send` is called, this queue - /// is drained. - packets_to_send: Vec>, - /// Whether the client registered with this - /// initial handler should be disconnected. - should_disconnect: bool, + /// A queue of actions to perform. When `actions_to_execute` + /// is called, the queue is flushed. + action_queue: Vec, /// If set to a value, indicates that encryption /// should be enabled with the given key. @@ -104,8 +113,7 @@ pub struct InitialHandler { impl InitialHandler { pub fn new(config: Arc, player_count: Arc) -> Self { Self { - packets_to_send: vec![], - should_disconnect: false, + action_queue: vec![], key: None, compression_threshold: None, @@ -126,10 +134,8 @@ impl InitialHandler { /// Notifies this initial handler of a packet /// received from the client. After calling this - /// function, `should_enable_encryption`, `packets_to_send`, `should_disconnect`, - /// `should_enable_compression`, - /// and `should_join`, in that order, - /// should be called to handle those various events. + /// function, `action_queue` should be called + /// and the actions should be executed in order. pub fn handle_packet(&mut self, packet: Box) { if self.stage == Stage::Finished { panic!("Called InitialHandler::handle_packet() after completion"); @@ -146,71 +152,13 @@ impl InitialHandler { } } - /// Returns a vector of packets to send to - /// the client. The internal queue is flushed - /// upon calling this function, meaning that - /// future calls to this function will return - /// an empty vector until `handle_packet` is called - /// again. - pub fn packets_to_send(&mut self) -> Vec> { + /// Returns a vector of actions to perform. + pub fn actions_to_execute(&mut self) -> Vec { let mut new_vec = vec![]; - std::mem::swap(&mut new_vec, &mut self.packets_to_send); + std::mem::swap(&mut new_vec, &mut self.action_queue); new_vec } - - /// Returns a boolean indicating whether - /// the client should be disconnected. - /// - /// If `true` is returned, the initial - /// handler should not be used further. - /// It should be dropped. - pub fn should_disconnect(&self) -> bool { - self.should_disconnect - } - - /// Returns an option indicating whether encryption should - /// be enabled. - /// - /// If `Some(key)` is returned, encryption should be enabled - /// with the given key. Future calls to this function will - /// return `None` to avoid enabling encryption multiple times. - /// - /// If `None` is returned, nothing needs to be done. - pub fn should_enable_encryption(&mut self) -> Option { - if let Some(key) = self.key { - self.key = None; - Some(key) - } else { - None - } - } - - /// Returns an option indicating whether compression should - /// be enabled. - /// - /// If `Some(threshold)` is returned, compression should be enabled - /// with the given threshold. Future calls to this function will - /// return `None` in order to avoid enabling compression multiple - /// times. - /// - /// if `None` is returned, nothing needs to be done. - pub fn should_enable_compression(&mut self) -> Option { - if let Some(threshold) = self.compression_threshold { - self.compression_threshold = None; - Some(threshold) - } else { - None - } - } - - /// Returns whether the player should be joined. - /// - /// If `Some` is returned, no further calls to `handle_packet` - /// should be made. - pub fn should_join(&self) -> Option { - self.info.clone() - } } /// Handles a packet, returning `Err` if the player @@ -285,7 +233,7 @@ fn handle_ping(ih: &mut InitialHandler, packet: &Ping) -> Result<(), Error> { send_packet(ih, pong); // After sending pong, we should disconnect. - ih.should_disconnect = true; + ih.action_queue.push(Action::Disconnect); ih.stage = Stage::Finished; Ok(()) @@ -302,7 +250,7 @@ fn handle_login_start(ih: &mut InitialHandler, packet: &LoginStart) -> Result<() // already finished, so we can call `finish` after // setting the player's info. if ih.config.server.online_mode { - // Enable encryption + // Start enabling encryption let encryption_request = EncryptionRequest::new( "".to_string(), // Server ID - always empty ih.rsa.public_key_to_der().unwrap(), @@ -353,6 +301,8 @@ fn handle_encryption_response( } ih.key = Some(key); + ih.action_queue + .push(Action::EnableEncryption(ih.key.clone().unwrap())); // Perform authentication let auth_result = mojang_api::server_auth( @@ -417,6 +367,8 @@ fn finish(ih: &mut InitialHandler) { info.username.clone(), ); send_packet(ih, login_success); + ih.action_queue + .push(Action::JoinGame(ih.info.clone().unwrap())); } /// Enables compression, sending the Set Compression @@ -424,6 +376,7 @@ fn finish(ih: &mut InitialHandler) { fn enable_compression(ih: &mut InitialHandler, threshold: i32) { ih.compression_threshold = Some(threshold); send_packet(ih, SetCompression::new(threshold)); + ih.action_queue.push(Action::EnableCompression(threshold)); } /// Checks that the initial handler stage matches @@ -440,7 +393,7 @@ fn check_stage(ih: &InitialHandler, expected: Stage, packet_ty: PacketType) -> R /// Disconnects the initial handler, sending /// a disconnect packet containing the reason. fn disconnect_login(ih: &mut InitialHandler, reason: &str) { - ih.should_disconnect = true; + ih.action_queue.push(Action::Disconnect); let json = json!({ "text": reason, @@ -453,7 +406,7 @@ fn disconnect_login(ih: &mut InitialHandler, reason: &str) { /// Adds a packet to the internal packet queue. fn send_packet(ih: &mut InitialHandler, packet: P) { - ih.packets_to_send.push(Box::new(packet)); + ih.action_queue.push(Action::SendPacket(Box::new(packet))); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -502,58 +455,23 @@ enum Stage { #[cfg(test)] mod tests { - use super::*; - use crate::PROTOCOL_VERSION; + use std::sync::atomic::AtomicUsize; + use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ Handshake, HandshakeState, LoginSuccess, Ping, Pong, Request, Response, SetCompression, }; use feather_core::network::packet::PacketType; - use std::sync::atomic::AtomicUsize; - #[test] - fn test_initial_handler_new() { - let mut ih = ih(); - - assert!(ih.packets_to_send().is_empty()); - assert!(!ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_none()); - } - - #[test] - fn test_packets_to_send_empty() { - let mut ih = ih(); - - // Fake some packets - ih.packets_to_send - .push(Box::new(Response::new("foo".to_string()))); - - let queue = ih.packets_to_send(); - assert_eq!(queue.len(), 1); - assert_eq!(queue.first().unwrap().ty(), PacketType::Response); - } - - #[test] - fn test_should_enable_encryption() { - let mut ih = ih(); - - assert!(ih.should_enable_encryption().is_none()); - - let key: Key = rand::random(); - ih.key = Some(key.clone()); + use crate::PROTOCOL_VERSION; - assert_eq!(key, ih.should_enable_encryption().unwrap()); - } + use super::*; #[test] - fn test_should_disconnect() { + fn test_initial_handler_new() { let mut ih = ih(); - assert!(!ih.should_disconnect()); - ih.should_disconnect = true; - - assert!(ih.should_disconnect()); + assert!(ih.actions_to_execute().is_empty()); } #[test] @@ -561,8 +479,6 @@ mod tests { let player_count = 24; let mut ih = ih_with_player_count(player_count); - assert!(!ih.should_disconnect()); - let handshake = Handshake::new( PROTOCOL_VERSION, "".to_string(), // Unused - server address @@ -572,44 +488,50 @@ mod tests { ih.handle_packet(Box::new(handshake)); // Confirm that no packets were sent and the player wasn't disconnected - assert!(ih.packets_to_send().is_empty()); - assert!(!ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_none()); + assert!(ih.actions_to_execute().is_empty()); let request = Request::new(); ih.handle_packet(Box::new(request)); - assert!(!ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_none()); + let actions = ih.actions_to_execute(); // Confirm that correct response was received - let sent_packets = ih.packets_to_send(); - assert_eq!(sent_packets.len(), 1); + assert_eq!(actions.len(), 1); - let _response = sent_packets.first().unwrap(); - assert_eq!(_response.ty(), PacketType::Response); + let _response = actions.first().unwrap(); + match _response { + Action::SendPacket(_response) => { + assert_eq!(_response.ty(), PacketType::Response); - let response = cast_packet::(&_response); - let _: serde_json::Value = serde_json::from_str(&response.json_response).unwrap(); + let response = cast_packet::(&_response); + let _: serde_json::Value = serde_json::from_str(&response.json_response).unwrap(); + } + _ => panic!(), + } // Send ping let payload = 39842; let ping = Ping::new(payload); ih.handle_packet(Box::new(ping)); - assert!(ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_none()); + let mut actions = ih.actions_to_execute(); - let sent_packets = ih.packets_to_send(); - assert_eq!(sent_packets.len(), 1); - let _pong = sent_packets.first().unwrap(); + assert_eq!(actions.len(), 2); + let _pong = actions.remove(0); + match _pong { + Action::SendPacket(_pong) => { + assert_eq!(_pong.ty(), PacketType::Pong); + let pong = cast_packet::(&_pong); + assert_eq!(pong.payload, payload); + } + _ => panic!(), + } - assert_eq!(_pong.ty(), PacketType::Pong); - let pong = cast_packet::(&_pong); - assert_eq!(pong.payload, payload); + let disconnect = actions.remove(0); + match disconnect { + Action::Disconnect => (), + _ => panic!(), + } } #[test] @@ -626,38 +548,52 @@ mod tests { ); ih.handle_packet(Box::new(handshake)); - let sent_packets = ih.packets_to_send(); - assert!(sent_packets.is_empty()); - assert!(!ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert!(ih.should_enable_compression().is_none()); + assert!(ih.actions_to_execute().is_empty()); let username = "test"; let login_start = LoginStart::new(username.to_string()); ih.handle_packet(Box::new(login_start)); - assert!(!ih.should_disconnect()); - assert!(ih.should_enable_encryption().is_none()); - assert_eq!( - ih.should_enable_compression(), - Some(config.io.compression_threshold) - ); + let mut actions = ih.actions_to_execute(); + assert_eq!(actions.len(), 4); - let mut sent_packets = ih.packets_to_send(); - assert_eq!(sent_packets.len(), 2); + let _set_compression = actions.remove(0); - let _set_compression = sent_packets.remove(0); - assert_eq!(_set_compression.ty(), PacketType::SetCompression); + match _set_compression { + Action::SendPacket(_set_compression) => { + assert_eq!(_set_compression.ty(), PacketType::SetCompression); - let set_compression = cast_packet::(&_set_compression); - assert_eq!(set_compression.threshold, config.io.compression_threshold); + let set_compression = cast_packet::(&_set_compression); + assert_eq!(set_compression.threshold, config.io.compression_threshold); + } + _ => panic!(), + } + + let enable_compression = actions.remove(0); + match enable_compression { + Action::EnableCompression(threshold) => { + assert_eq!(threshold, config.io.compression_threshold); + } + _ => panic!(), + } + + let _login_success = actions.remove(0); - let _login_success = sent_packets.first().unwrap(); - assert_eq!(_login_success.ty(), PacketType::LoginSuccess); + match _login_success { + Action::SendPacket(_login_success) => { + assert_eq!(_login_success.ty(), PacketType::LoginSuccess); - let login_success = cast_packet::(&_login_success); - assert_eq!(login_success.username, username.to_string()); - assert!(ih.should_join().is_some()); + let login_success = cast_packet::(&_login_success); + assert_eq!(login_success.username, username.to_string()); + } + _ => panic!(), + } + + let join = actions.remove(0); + match join { + Action::JoinGame(_) => (), + _ => panic!(), + } } fn ih() -> InitialHandler { diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 4ff2ad1b3..370fe7c07 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -1,18 +1,22 @@ -use super::initialhandler as ih; -use super::*; -use crate::config::Config; -use crate::io::initialhandler::InitialHandler; -use crate::PlayerCount; +use std::io::Read; +use std::io::Write; +use std::sync::Arc; + use bytes::BufMut; -use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; -use feather_core::network::packet::PacketDirection; -use feather_core::network::serialize::ConnectionIOManager; use hashbrown::HashMap; use mio::Event; use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; -use std::io::Read; -use std::io::Write; -use std::sync::Arc; + +use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; +use feather_core::network::packet::PacketDirection; +use feather_core::network::serialize::ConnectionIOManager; + +use crate::config::Config; +use crate::io::initialhandler::{Action, InitialHandler}; +use crate::PlayerCount; + +use super::initialhandler as ih; +use super::*; // The token used to listen on the channel receiving messages from the listener thread const LISTENER_TOKEN: Token = Token(0); @@ -217,7 +221,7 @@ fn disconnect_client(worker: &mut Worker, client_id: Client) { worker.clients.remove(&client_id); } -fn send_packet(worker: &mut Worker, client_id: Client, packet: Box) { +fn send_packet(worker: &mut Worker, client_id: Client, packet: Box) { let client = worker.clients.get_mut(&client_id).unwrap(); let manager = &mut client.manager; @@ -302,81 +306,69 @@ fn write_to_client(worker: &mut Worker, client_id: Client) -> Result<(), ()> { Ok(()) } -fn handle_packet(worker: &mut Worker, client_id: Client, packet: Box) { +fn handle_packet(worker: &mut Worker, client_id: Client, packet: Box) { let client = worker.clients.get_mut(&client_id).unwrap(); - let mut packets_to_send = vec![]; - let mut should_disconnect = false; - let mut encryption_key = None; - let mut compression_threshold = None; - let mut should_join = None; + let mut action_queue = vec![]; if let Some(ih) = client.initial_handler.as_mut() { // Forward packet to the initial handler. ih.handle_packet(packet); - encryption_key = ih.should_enable_encryption(); - packets_to_send = ih.packets_to_send(); - should_disconnect = ih.should_disconnect(); - compression_threshold = ih.should_enable_compression(); - should_join = ih.should_join(); + action_queue = ih.actions_to_execute(); } else { // Forward packet to the server. let msg = ServerToWorkerMessage::NotifyPacketReceived(packet); client.sender.as_ref().unwrap().send(msg).unwrap(); } - if let Some(key) = encryption_key { - client.manager.enable_encryption(key); - } + for action in action_queue { + let client = worker.clients.get_mut(&client_id).unwrap(); - packets_to_send - .into_iter() - .for_each(|packet| send_packet(worker, client_id, packet)); - - let client = worker.clients.get_mut(&client_id).unwrap(); - - if should_disconnect { - disconnect_client(worker, client_id); - return; - } - - if let Some(threshold) = compression_threshold { - client.manager.enable_compression(threshold as usize); - } - - if let Some(info) = should_join { - let (send1, recv1) = channel(); - let (send2, recv2) = channel(); - - let player_info = NewClientInfo { - ip: client.addr.clone(), - username: info.username, - profile: info.props, - uuid: info.uuid, - sender: send1, - receiver: recv2, - }; - - worker - .sender - .send(ListenerToWorkerMessage::NewClient(player_info)) - .unwrap(); - - client.initial_handler = None; - - client.sender = Some(send2); - client.receiver = Some(recv1); - - worker - .poll - .register( - client.receiver.as_ref().unwrap(), - client.server_to_worker_token, - Ready::readable(), - PollOpt::edge(), - ) - .unwrap(); + match action { + Action::Disconnect => { + disconnect_client(worker, client_id); + return; + } + Action::EnableCompression(threshold) => { + client.manager.enable_compression(threshold as usize); + } + Action::JoinGame(info) => { + let (send1, recv1) = channel(); + let (send2, recv2) = channel(); + + let player_info = NewClientInfo { + ip: client.addr.clone(), + username: info.username, + profile: info.props, + uuid: info.uuid, + sender: send1, + receiver: recv2, + }; + + worker + .sender + .send(ListenerToWorkerMessage::NewClient(player_info)) + .unwrap(); + + client.initial_handler = None; + + client.sender = Some(send2); + client.receiver = Some(recv1); + + worker + .poll + .register( + client.receiver.as_ref().unwrap(), + client.server_to_worker_token, + Ready::readable(), + PollOpt::edge(), + ) + .unwrap(); + } + Action::EnableEncryption(key) => client.manager.enable_encryption(key), + Action::SendPacket(packet) => send_packet(worker, client_id, packet), + } } } diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs index b57d3abec..9f090e342 100644 --- a/server/src/joinhandler.rs +++ b/server/src/joinhandler.rs @@ -5,23 +5,26 @@ //! packets to join the player. After completion, the component is //! removed. -use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; -use crate::config::Config; -use crate::network::NetworkComponent; -use crate::player::ChunkPendingComponent; -use crate::PlayerCount; -use feather_core::network::packet::implementation::{ - JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, -}; -use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; -use feather_core::{Difficulty, Dimension, Gamemode}; +use std::sync::atomic::Ordering; +use std::sync::Arc; + use shrev::EventChannel; use specs::{ Component, Entities, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, ReaderId, System, World, WriteStorage, }; -use std::sync::atomic::Ordering; -use std::sync::Arc; + +use feather_core::network::packet::implementation::{ + JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, +}; +use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; +use feather_core::{Difficulty, Dimension, Gamemode}; + +use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; +use crate::config::Config; +use crate::network::NetworkComponent; +use crate::player::ChunkPendingComponent; +use crate::PlayerCount; /// For now, we use a fixed spawn position. /// In the future, the spawn position should diff --git a/server/src/main.rs b/server/src/main.rs index deef4db01..3fcb1a8eb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,6 +6,17 @@ extern crate serde_derive; extern crate serde_json; use std::alloc::System; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use specs::{Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, WorldExt}; + +use feather_core::network::packet::implementation::DisconnectPlay; +use prelude::*; + +use crate::entity::EntityComponent; +use crate::network::send_packet_to_player; #[global_allocator] static ALLOC: System = System; @@ -23,15 +34,6 @@ pub mod prelude; pub mod testframework; pub mod worldupdate; -use crate::entity::EntityComponent; -use crate::network::send_packet_to_player; -use feather_core::network::packet::implementation::DisconnectPlay; -use prelude::*; -use specs::{Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, WorldExt}; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - pub const TPS: u64 = 20; pub const PROTOCOL_VERSION: u32 = 404; pub const SERVER_VERSION: &'static str = "Feather 1.13.2"; diff --git a/server/src/network.rs b/server/src/network.rs index 8bdfd37cd..6ef9b7f80 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,17 +1,20 @@ -use crate::entity::{EntityComponent, PlayerComponent}; -use crate::io::{NetworkIoManager, NewClientInfo, ServerToListenerMessage, ServerToWorkerMessage}; -use crate::joinhandler::JoinHandlerComponent; -use crate::prelude::*; -use crate::TickCount; -use feather_core::entitymeta::{EntityMetadata, MetaEntry}; -use feather_core::network::packet::{implementation::*, Packet, PacketType}; +use std::sync::Mutex; + use mio_extras::channel::{Receiver, Sender}; use shrev::EventChannel; use specs::{ Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, WorldExt, Write, WriteStorage, }; -use std::sync::Mutex; + +use feather_core::entitymeta::{EntityMetadata, MetaEntry}; +use feather_core::network::packet::{implementation::*, Packet, PacketType}; + +use crate::entity::{EntityComponent, PlayerComponent}; +use crate::io::{NetworkIoManager, NewClientInfo, ServerToListenerMessage, ServerToWorkerMessage}; +use crate::joinhandler::JoinHandlerComponent; +use crate::prelude::*; +use crate::TickCount; //const MAX_KEEP_ALIVE_TIME: u64 = 30; //const HEAD_OFFSET: f64 = 1.62; // Offset from feet pos to head pos diff --git a/server/src/player.rs b/server/src/player.rs index 052eb7178..448044979 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -2,18 +2,8 @@ //! relating to players, including player movement //! and inventory handling. -use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; -use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; -use crate::joinhandler::SPAWN_POSITION; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerJoinEvent}; -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{ - ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, -}; -use feather_core::network::packet::{Packet, PacketType}; -use feather_core::world::chunk::Chunk; -use feather_core::world::{ChunkMap, ChunkPosition, Position}; -use feather_core::Gamemode; +use std::ops::{Deref, DerefMut}; + use hashbrown::HashSet; use rayon::prelude::*; use shrev::EventChannel; @@ -22,7 +12,20 @@ use specs::{ Component, Entities, Entity, LazyUpdate, ParJoin, Read, ReadStorage, ReaderId, System, World, WorldExt, WriteStorage, }; -use std::ops::{Deref, DerefMut}; + +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::{ + ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, +}; +use feather_core::network::packet::{Packet, PacketType}; +use feather_core::world::chunk::Chunk; +use feather_core::world::{ChunkMap, ChunkPosition, Position}; +use feather_core::Gamemode; + +use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; +use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; +use crate::joinhandler::SPAWN_POSITION; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerJoinEvent}; /// System for handling player movement /// packets. diff --git a/server/src/testframework.rs b/server/src/testframework.rs index 35ae5a04b..48873a724 100644 --- a/server/src/testframework.rs +++ b/server/src/testframework.rs @@ -1,20 +1,23 @@ //! Helper framework for writing unit tests. +use std::net::TcpListener; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; + +use mio_extras::channel::{channel, Receiver, Sender}; +use rand::Rng; +use specs::{Builder, Dispatcher, Entity, World, WorldExt}; +use uuid::Uuid; + +use feather_core::network::packet::{Packet, PacketType}; +use feather_core::world::Position; +use feather_core::Gamemode; + use crate::config::Config; use crate::entity::{EntityComponent, PlayerComponent}; use crate::io::ServerToWorkerMessage; use crate::network::{NetworkComponent, PacketQueue}; use crate::PlayerCount; -use feather_core::network::packet::{Packet, PacketType}; -use feather_core::world::Position; -use feather_core::Gamemode; -use mio_extras::channel::{channel, Receiver, Sender}; -use rand::Rng; -use specs::{Builder, Dispatcher, Entity, World, WorldExt}; -use std::net::TcpListener; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; -use uuid::Uuid; /// Initializes a Specs world and dispatcher /// using default configuration options and an @@ -118,10 +121,11 @@ fn find_open_port() -> Option { /// all other tests would fail if the testing /// framework didn't work. mod tests { - use super::*; use crate::entity::{EntityComponent, PlayerComponent}; use crate::network::NetworkComponent; + use super::*; + #[test] fn test_find_open_port() { let port = find_open_port().unwrap(); diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 599e6b7dc..104dfe995 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -1,9 +1,8 @@ //! This module handles packets relating to world updates, //! including block update packets. -use crate::disconnect_player; -use crate::entity::PlayerComponent; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; +use specs::{Entity, Join, LazyUpdate, Read, ReadStorage, System, Write}; + use feather_core::network::cast_packet; use feather_core::network::packet::implementation::BlockChange; use feather_core::network::packet::implementation::{PlayerDigging, PlayerDiggingStatus}; @@ -11,7 +10,10 @@ use feather_core::network::packet::PacketType; use feather_core::world::block::{Block, BlockToId}; use feather_core::world::{BlockPosition, ChunkMap}; use feather_core::Gamemode; -use specs::{Entity, Join, LazyUpdate, Read, ReadStorage, System, Write}; + +use crate::disconnect_player; +use crate::entity::PlayerComponent; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; /// System responsible for polling for PlayerDigging /// packets and handling them accordingly. @@ -96,11 +98,14 @@ fn broadcast_block_update( #[cfg(test)] mod tests { - use super::*; - use crate::testframework as t; + use specs::{RunNow, WorldExt}; + use feather_core::world::chunk::Chunk; use feather_core::world::ChunkPosition; - use specs::{RunNow, WorldExt}; + + use crate::testframework as t; + + use super::*; #[test] fn test_system() { From 372f10718b857c7b27eba167281a91b0a97696c5 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 15:20:17 -0600 Subject: [PATCH 24/30] Get Specs refactor to allow player joining --- core/src/network/mctypes.rs | 8 ++++---- core/src/network/packet/mod.rs | 5 +++++ server/config/feather.toml | 2 +- server/src/io/worker.rs | 4 +++- server/src/joinhandler.rs | 7 ++++++- server/src/main.rs | 10 ++++++---- server/src/player.rs | 3 ++- server/src/worldupdate.rs | 18 ++++++++++++++++-- 8 files changed, 43 insertions(+), 14 deletions(-) diff --git a/core/src/network/mctypes.rs b/core/src/network/mctypes.rs index 707d4d7a1..71649fbed 100644 --- a/core/src/network/mctypes.rs +++ b/core/src/network/mctypes.rs @@ -150,11 +150,11 @@ impl McTypeRead for T { return Err(()); } let val = self.get_u64_be(); - let x = val >> 38; - let y = (val >> 26) & 0xFFF; - let z = val << 38 >> 38; + let x = val as i32 >> 38; + let y = (val as i32 >> 26) & 0xFFF; + let z = (val as i32) << 38 >> 38; - Ok(BlockPosition::new(x as i32, y as i32, z as i32)) + Ok(BlockPosition::new(x, y, z)) } fn read_bool(&mut self) -> Result { diff --git a/core/src/network/packet/mod.rs b/core/src/network/packet/mod.rs index d45ebf0aa..fae482fad 100644 --- a/core/src/network/packet/mod.rs +++ b/core/src/network/packet/mod.rs @@ -437,6 +437,11 @@ lazy_static! { PacketType::Pong, ); + m.insert( + PacketId(0x1B, PacketDirection::Clientbound, PacketStage::Play), + PacketType::DisconnectPlay, + ); + m.insert( PacketId(0x21, PacketDirection::Clientbound, PacketStage::Play), PacketType::KeepAliveClientbound, diff --git a/server/config/feather.toml b/server/config/feather.toml index 1b4a6fb48..4d575eefc 100644 --- a/server/config/feather.toml +++ b/server/config/feather.toml @@ -29,4 +29,4 @@ nerf_spawner_mobs = false pvp_style = "classic" [log] -level = "trace" +level = "debug" diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 370fe7c07..ad197e9d2 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -8,7 +8,7 @@ use mio::Event; use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; -use feather_core::network::packet::PacketDirection; +use feather_core::network::packet::{PacketDirection, PacketType}; use feather_core::network::serialize::ConnectionIOManager; use crate::config::Config; @@ -17,6 +17,8 @@ use crate::PlayerCount; use super::initialhandler as ih; use super::*; +use feather_core::network::cast_packet; +use feather_core::network::packet::implementation::PlayerDigging; // The token used to listen on the channel receiving messages from the listener thread const LISTENER_TOKEN: Token = Token(0); diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs index 9f090e342..55428910d 100644 --- a/server/src/joinhandler.rs +++ b/server/src/joinhandler.rs @@ -93,7 +93,9 @@ impl<'a> System<'a> for JoinHandlerSystem { let mut to_remove = vec![]; - for (player, net, join_handler) in (&entities, &netcomps, &mut joincomps).join() { + for (player, net, join_handler, pending_chunks) in + (&entities, &netcomps, &mut joincomps, &pending_chunks).join() + { match join_handler.stage { Stage::Initial => { // Send Join Game, then queue chunks for loading + sending. @@ -132,6 +134,9 @@ impl<'a> System<'a> for JoinHandlerSystem { Stage::AwaitChunkSends => { // If 0 chunks have yet to be sent, join the player by sending spawn position. // See https://wiki.vg/Protocol_FAQ + if pending_chunks.len() != 0 { + continue; + } let spawn_position = SpawnPosition::new(COMPASS_SPAWN_POSITION); crate::network::send_packet_to_player(net, spawn_position); diff --git a/server/src/main.rs b/server/src/main.rs index 3fcb1a8eb..b33d2818b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -182,12 +182,14 @@ pub fn current_time_in_millis() -> u64 { /// Disconnects the given player, removing them from the world. /// This operation is performed lazily. -pub fn disconnect_player(player: Entity, reason: &'static str, lazy: &LazyUpdate) { +pub fn disconnect_player(player: Entity, reason: String, lazy: &LazyUpdate) { lazy.exec_mut(move |world| { - let packet = DisconnectPlay::new(reason.to_string()); - send_packet_to_player(world.read_component().get(player).unwrap(), packet); + let json = json!({ + "text": reason, + }); - //network::broadcast_player_leave(world.read_component(), player); + let packet = DisconnectPlay::new(json.to_string()); + send_packet_to_player(world.read_component().get(player).unwrap(), packet); if let Some(ecomp) = world.read_component::().get(player) { info!("Disconnected player {}: {}", ecomp.display_name, reason); diff --git a/server/src/player.rs b/server/src/player.rs index 448044979..4af083b11 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -22,7 +22,7 @@ use feather_core::world::chunk::Chunk; use feather_core::world::{ChunkMap, ChunkPosition, Position}; use feather_core::Gamemode; -use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; +use crate::chunkclient::{load_chunk, ChunkLoadEvent, ChunkWorkerHandle}; use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; use crate::joinhandler::SPAWN_POSITION; use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerJoinEvent}; @@ -271,6 +271,7 @@ pub fn send_chunk_to_player( send_chunk_data(chunk, net); } else { // Queue for loading + load_chunk(chunk_handle, chunk_pos); lazy.exec_mut(move |world| { world .write_component::() diff --git a/server/src/worldupdate.rs b/server/src/worldupdate.rs index 104dfe995..533d65af3 100644 --- a/server/src/worldupdate.rs +++ b/server/src/worldupdate.rs @@ -56,13 +56,27 @@ fn handle_player_digging( PlayerDiggingStatus::StartedDigging => { if pcomp.gamemode == Gamemode::Creative { if handle_block_break(chunk_map, packet.location, &netcomps, &pcomps).is_err() { - disconnect_player(player, "Attempted to break block in unloaded chunk", &lazy); + disconnect_player( + player, + format!( + "Attempted to break block in unloaded chunk ({:?})", + packet.location + ), + &lazy, + ); } } } PlayerDiggingStatus::FinishedDigging => { if handle_block_break(chunk_map, packet.location, &netcomps, &pcomps).is_err() { - disconnect_player(player, "Attempted to break block in unloaded chunk", &lazy); + disconnect_player( + player, + format!( + "Attempted to break block in unloaded chunk ({:?})", + packet.location + ), + &lazy, + ); } } status => warn!("Unhandled Player Digging status: {:?}", status), From 953119cbce039a1c60f45d4ce00e7f83c115cf83 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 15:38:14 -0600 Subject: [PATCH 25/30] Send Player Info and Spawn Player when a player joins --- server/src/joinhandler.rs | 18 ++++- server/src/main.rs | 5 ++ server/src/network.rs | 76 ++------------------ server/src/player.rs | 141 +++++++++++++++++++++++++++++++++++--- 4 files changed, 159 insertions(+), 81 deletions(-) diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs index 55428910d..abcd420e7 100644 --- a/server/src/joinhandler.rs +++ b/server/src/joinhandler.rs @@ -10,13 +10,14 @@ use std::sync::Arc; use shrev::EventChannel; use specs::{ - Component, Entities, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, ReaderId, System, - World, WriteStorage, + Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, ReaderId, + System, World, Write, WriteStorage, }; use feather_core::network::packet::implementation::{ JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, }; +use feather_core::network::packet::PacketType::Player; use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; use feather_core::{Difficulty, Dimension, Gamemode}; @@ -62,6 +63,13 @@ impl Component for JoinHandlerComponent { type Storage = HashMapStorage; } +/// Event which is triggered when a player +/// completes the join process (i.e. when +/// all chunks have been sent). +pub struct PlayerJoinEvent { + pub player: Entity, +} + /// System for join handling. pub struct JoinHandlerSystem; @@ -70,6 +78,7 @@ impl<'a> System<'a> for JoinHandlerSystem { WriteStorage<'a, JoinHandlerComponent>, ReadStorage<'a, NetworkComponent>, ReadStorage<'a, ChunkPendingComponent>, + Write<'a, EventChannel>, Read<'a, ChunkWorkerHandle>, Entities<'a>, Read<'a, LazyUpdate>, @@ -83,6 +92,7 @@ impl<'a> System<'a> for JoinHandlerSystem { mut joincomps, netcomps, pending_chunks, + mut join_events, worker_handle, entities, lazy, @@ -151,6 +161,10 @@ impl<'a> System<'a> for JoinHandlerSystem { ); crate::network::send_packet_to_player(net, position_and_look); + // Trigger event + let event = PlayerJoinEvent { player }; + join_events.single_write(event); + // We're finished here. to_remove.push(player); } diff --git a/server/src/main.rs b/server/src/main.rs index b33d2818b..eebdb1d26 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -142,6 +142,11 @@ fn init_world<'a, 'b>( &["chunk_send"], ) .with(player::PlayerInitSystem::new(), "player_init", &["network"]) + .with( + player::JoinBroadcastSystem::new(), + "join_broadcast", + &["join_handler", "player_init"], + ) .build(); dispatcher.setup(&mut world); diff --git a/server/src/network.rs b/server/src/network.rs index 6ef9b7f80..4040f1480 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -113,8 +113,9 @@ impl Component for NetworkComponent { /// other systems can handle them. pub struct NetworkSystem; -/// Event which is triggered when a player joins. -pub struct PlayerJoinEvent { +/// Event which is triggered when a player joins +/// but before the join handler is completed. +pub struct PlayerPreJoinEvent { pub player: Entity, pub username: String, pub uuid: Uuid, @@ -125,7 +126,7 @@ impl<'a> System<'a> for NetworkSystem { type SystemData = ( WriteStorage<'a, NetworkComponent>, ReadStorage<'a, PlayerComponent>, - Write<'a, EventChannel>, + Write<'a, EventChannel>, Write<'a, PacketQueue>, Read<'a, NetworkIoManager>, Entities<'a>, @@ -166,7 +167,7 @@ impl<'a> System<'a> for NetworkSystem { }); // Queue event - let event = PlayerJoinEvent { + let event = PlayerPreJoinEvent { player: new_entity, username: info.username.clone(), uuid: info.uuid.clone(), @@ -207,69 +208,6 @@ impl<'a> System<'a> for NetworkSystem { } } -/// Returns the player info and spawn player packets -/// for the given player. -pub fn get_player_initialization_packets( - ecomp: &EntityComponent, - pcomp: &PlayerComponent, - player: Entity, -) -> (PlayerInfo, SpawnPlayer) { - let display_name = json!({ - "text": ecomp.display_name - }) - .to_string(); - - let mut props = vec![]; - for prop in pcomp.profile_properties.iter() { - props.push(( - prop.name.clone(), - prop.value.clone(), - prop.signature.clone(), - )); - } - - let action = PlayerInfoAction::AddPlayer( - ecomp.display_name.clone(), - props, - Gamemode::Creative, - 50, - display_name, - ); - let player_info = PlayerInfo::new(action, ecomp.uuid.clone()); - - let metadata = EntityMetadata::new().with(&[ - (0, MetaEntry::Byte(0)), - (1, MetaEntry::VarInt(300)), - (2, MetaEntry::OptChat(None)), - (3, MetaEntry::Boolean(false)), - (4, MetaEntry::Boolean(false)), - (5, MetaEntry::Boolean(false)), - (6, MetaEntry::Byte(0)), - (7, MetaEntry::Float(1.0)), - (8, MetaEntry::VarInt(0)), - (9, MetaEntry::Boolean(false)), - (10, MetaEntry::VarInt(0)), - (11, MetaEntry::Float(0.0)), - (12, MetaEntry::VarInt(0)), - (13, MetaEntry::Byte(0)), - (14, MetaEntry::Byte(1)), - // TODO NBT - ]); - - let spawn_player = SpawnPlayer::new( - player.id() as i32, - ecomp.uuid.clone(), - ecomp.position.x, - ecomp.position.y, - ecomp.position.z, - degrees_to_stops(ecomp.position.pitch), - degrees_to_stops(ecomp.position.yaw), - metadata, - ); - - (player_info, spawn_player) -} - /// Sends a packet to all (joined) players on the server, excluding /// `neq`, if it exists. pub fn send_packet_to_all_players( @@ -301,7 +239,3 @@ pub fn send_packet_to_player(comp: &NetworkComponent, packe pub fn send_packet_boxed_to_player(comp: &NetworkComponent, packet: Box) { let _ = comp.sender.send(ServerToWorkerMessage::SendPacket(packet)); } - -pub fn degrees_to_stops(degs: f32) -> u8 { - ((degs / 360.0) * 256.) as u8 -} diff --git a/server/src/player.rs b/server/src/player.rs index 4af083b11..d4d61102e 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -9,13 +9,14 @@ use rayon::prelude::*; use shrev::EventChannel; use specs::storage::BTreeStorage; use specs::{ - Component, Entities, Entity, LazyUpdate, ParJoin, Read, ReadStorage, ReaderId, System, World, - WorldExt, WriteStorage, + Component, Entities, Entity, Join, LazyUpdate, ParJoin, Read, ReadStorage, ReaderId, System, + World, WorldExt, WriteStorage, }; use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ - ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, + ChunkData, PlayerInfo, PlayerInfoAction, PlayerLook, PlayerPosition, + PlayerPositionAndLookServerbound, SpawnPlayer, }; use feather_core::network::packet::{Packet, PacketType}; use feather_core::world::chunk::Chunk; @@ -24,8 +25,9 @@ use feather_core::Gamemode; use crate::chunkclient::{load_chunk, ChunkLoadEvent, ChunkWorkerHandle}; use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent}; -use crate::joinhandler::SPAWN_POSITION; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerJoinEvent}; +use crate::joinhandler::{PlayerJoinEvent, SPAWN_POSITION}; +use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerPreJoinEvent}; +use feather_core::entitymeta::{EntityMetadata, MetaEntry}; /// System for handling player movement /// packets. @@ -139,7 +141,7 @@ impl Component for ChunkPendingComponent { /// System for initializing the necessary components /// when a player joins. pub struct PlayerInitSystem { - join_event_reader: Option>, + join_event_reader: Option>, } impl PlayerInitSystem { @@ -152,7 +154,7 @@ impl PlayerInitSystem { impl<'a> System<'a> for PlayerInitSystem { type SystemData = ( - Read<'a, EventChannel>, + Read<'a, EventChannel>, WriteStorage<'a, PlayerComponent>, WriteStorage<'a, EntityComponent>, WriteStorage<'a, ChunkPendingComponent>, @@ -190,7 +192,7 @@ impl<'a> System<'a> for PlayerInitSystem { self.join_event_reader = Some( world - .fetch_mut::>() + .fetch_mut::>() .register_reader(), ); } @@ -250,6 +252,125 @@ impl<'a> System<'a> for ChunkSendSystem { } } +/// System for broadcasting when a player joins +/// the game. +pub struct JoinBroadcastSystem { + reader: Option>, +} + +impl JoinBroadcastSystem { + pub fn new() -> Self { + Self { reader: None } + } +} + +impl<'a> System<'a> for JoinBroadcastSystem { + type SystemData = ( + Read<'a, EventChannel>, + ReadStorage<'a, EntityComponent>, + ReadStorage<'a, PlayerComponent>, + ReadStorage<'a, NetworkComponent>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (join_events, entity_comps, player_comps, net_comps, entities) = data; + + for event in join_events.read(&mut self.reader.as_mut().unwrap()) { + // Broadcast join + let entity_comp = entity_comps.get(event.player).unwrap(); + let player_comp = player_comps.get(event.player).unwrap(); + + let (player_info, spawn_player) = + get_player_initialization_packets(entity_comp, player_comp, event.player); + + for (player, net) in (&entities, &net_comps).join() { + // Send player info to the player who joined + // so they can see themselves in the tablist, + // but don't send spawn player. + send_packet_to_player(net, player_info.clone()); + if player != event.player { + send_packet_to_player(net, spawn_player.clone()); + } + } + } + } + + fn setup(&mut self, world: &mut World) { + use specs::SystemData; + Self::SystemData::setup(world); + + self.reader = Some( + world + .fetch_mut::>() + .register_reader(), + ); + } +} + +/// Returns the player info and spawn player packets +/// for the given player. +fn get_player_initialization_packets( + ecomp: &EntityComponent, + pcomp: &PlayerComponent, + player: Entity, +) -> (PlayerInfo, SpawnPlayer) { + let display_name = json!({ + "text": ecomp.display_name + }) + .to_string(); + + let mut props = vec![]; + for prop in pcomp.profile_properties.iter() { + props.push(( + prop.name.clone(), + prop.value.clone(), + prop.signature.clone(), + )); + } + + let action = PlayerInfoAction::AddPlayer( + ecomp.display_name.clone(), + props, + Gamemode::Creative, + 50, + display_name, + ); + let player_info = PlayerInfo::new(action, ecomp.uuid.clone()); + + let metadata = EntityMetadata::new().with(&[ + (0, MetaEntry::Byte(0)), + (1, MetaEntry::VarInt(300)), + (2, MetaEntry::OptChat(None)), + (3, MetaEntry::Boolean(false)), + (4, MetaEntry::Boolean(false)), + (5, MetaEntry::Boolean(false)), + (6, MetaEntry::Byte(0)), + (7, MetaEntry::Float(1.0)), + (8, MetaEntry::VarInt(0)), + (9, MetaEntry::Boolean(false)), + (10, MetaEntry::VarInt(0)), + (11, MetaEntry::Float(0.0)), + (12, MetaEntry::VarInt(0)), + (13, MetaEntry::Byte(0)), + (14, MetaEntry::Byte(1)), + // TODO NBT + ]); + + let spawn_player = SpawnPlayer::new( + player.id() as i32, + ecomp.uuid.clone(), + ecomp.position.x, + ecomp.position.y, + ecomp.position.z, + degrees_to_stops(ecomp.position.pitch), + degrees_to_stops(ecomp.position.yaw), + metadata, + ); + + (player_info, spawn_player) +} + fn send_chunk_data(chunk: &Chunk, net: &NetworkComponent) { let packet = ChunkData::new(chunk.clone()); send_packet_to_player(net, packet); @@ -282,3 +403,7 @@ pub fn send_chunk_to_player( }); } } + +fn degrees_to_stops(degs: f32) -> u8 { + ((degs / 360.0) * 256.) as u8 +} From 2e1eb45cc0d3a2bbcfee7bda5bc621b6830e45e1 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 15:42:04 -0600 Subject: [PATCH 26/30] Send existing players to joining players --- server/src/player.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server/src/player.rs b/server/src/player.rs index d4d61102e..cecf44e6e 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -253,7 +253,8 @@ impl<'a> System<'a> for ChunkSendSystem { } /// System for broadcasting when a player joins -/// the game. +/// the game. Also spawns other players to +/// the player's client. pub struct JoinBroadcastSystem { reader: Option>, } @@ -293,6 +294,20 @@ impl<'a> System<'a> for JoinBroadcastSystem { send_packet_to_player(net, spawn_player.clone()); } } + + let net_comp = net_comps.get(event.player).unwrap(); + + // Send existing players to new player + for (entity_comp, player_comp, entity) in + (&entity_comps, &player_comps, &entities).join() + { + if entity != event.player { + let (player_info, spawn_player) = + get_player_initialization_packets(entity_comp, player_comp, entity); + send_packet_to_player(net_comp, player_info); + send_packet_to_player(net_comp, spawn_player); + } + } } } From 519f12baa56c46e89decb0d22d85693f40c2933f Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 15:48:54 -0600 Subject: [PATCH 27/30] Fix player count not being decremented --- server/src/main.rs | 22 ++++++++++++++++------ server/src/network.rs | 10 ++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index eebdb1d26..14ae5bdbc 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,7 +6,7 @@ extern crate serde_derive; extern crate serde_json; use std::alloc::System; -use std::sync::atomic::AtomicUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -196,14 +196,24 @@ pub fn disconnect_player(player: Entity, reason: String, lazy: &LazyUpdate) { let packet = DisconnectPlay::new(json.to_string()); send_packet_to_player(world.read_component().get(player).unwrap(), packet); - if let Some(ecomp) = world.read_component::().get(player) { - info!("Disconnected player {}: {}", ecomp.display_name, reason); - } - - world.delete_entity(player).unwrap(); + disconnect_player_without_packet(player, world, reason); }) } +/// Disconnects a player without sending Disconnect Play. +/// This should be used when the client disconnects. +pub fn disconnect_player_without_packet(player: Entity, world: &mut World, reason: String) { + if let Some(ecomp) = world.read_component::().get(player) { + info!("Disconnected player {}: {}", ecomp.display_name, reason); + } + + world.delete_entity(player).unwrap(); + + // Decrement player count + let player_count = world.fetch_mut::>(); + player_count.0.fetch_sub(1, Ordering::SeqCst); +} + #[cfg(test)] mod tests { use super::*; diff --git a/server/src/network.rs b/server/src/network.rs index 4040f1480..4c5e484d8 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -14,7 +14,7 @@ use crate::entity::{EntityComponent, PlayerComponent}; use crate::io::{NetworkIoManager, NewClientInfo, ServerToListenerMessage, ServerToWorkerMessage}; use crate::joinhandler::JoinHandlerComponent; use crate::prelude::*; -use crate::TickCount; +use crate::{disconnect_player_without_packet, TickCount}; //const MAX_KEEP_ALIVE_TIME: u64 = 30; //const HEAD_OFFSET: f64 = 1.62; // Offset from feet pos to head pos @@ -188,7 +188,13 @@ impl<'a> System<'a> for NetworkSystem { } ServerToWorkerMessage::NotifyDisconnect => { // TODO broadcast disconnect - entities.delete(player).unwrap(); + lazy.exec_mut(move |world| { + disconnect_player_without_packet( + player, + world, + "Client disconnected".to_string(), + ) + }); } _ => panic!("Network system received invalid message from IO worker}"), } From 60aad630188e3abacaa4cb11451c16458bb4c7cb Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 18:17:15 -0600 Subject: [PATCH 28/30] Broadcast when a player disconnects --- server/src/io/initialhandler.rs | 4 +-- server/src/io/worker.rs | 4 +++ server/src/main.rs | 32 +++++++++++++++---- server/src/player.rs | 55 ++++++++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index 9ad6e433d..63afbad7b 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -393,8 +393,6 @@ fn check_stage(ih: &InitialHandler, expected: Stage, packet_ty: PacketType) -> R /// Disconnects the initial handler, sending /// a disconnect packet containing the reason. fn disconnect_login(ih: &mut InitialHandler, reason: &str) { - ih.action_queue.push(Action::Disconnect); - let json = json!({ "text": reason, }) @@ -402,6 +400,8 @@ fn disconnect_login(ih: &mut InitialHandler, reason: &str) { let packet = DisconnectLogin::new(json); send_packet(ih, packet); + + ih.action_queue.push(Action::Disconnect); } /// Adds a packet to the internal packet queue. diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index ad197e9d2..86afb4fc0 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -275,6 +275,10 @@ fn read_from_stream(worker: &mut Worker, token: Token) -> Result<(), ()> { .take_pending_packets() { handle_packet(worker, client_id, packet); + if !worker.clients.contains_key(&client_id) { + // Client was disconnected + return Ok(()); + } } Ok(()) diff --git a/server/src/main.rs b/server/src/main.rs index 14ae5bdbc..1d0ac91d7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,6 +17,8 @@ use prelude::*; use crate::entity::EntityComponent; use crate::network::send_packet_to_player; +use crate::player::PlayerDisconnectEvent; +use shrev::EventChannel; #[global_allocator] static ALLOC: System = System; @@ -147,6 +149,11 @@ fn init_world<'a, 'b>( "join_broadcast", &["join_handler", "player_init"], ) + .with( + player::DisconnectBroadcastSystem::new(), + "disconnect_broadcast", + &[], + ) .build(); dispatcher.setup(&mut world); @@ -203,15 +210,28 @@ pub fn disconnect_player(player: Entity, reason: String, lazy: &LazyUpdate) { /// Disconnects a player without sending Disconnect Play. /// This should be used when the client disconnects. pub fn disconnect_player_without_packet(player: Entity, world: &mut World, reason: String) { - if let Some(ecomp) = world.read_component::().get(player) { - info!("Disconnected player {}: {}", ecomp.display_name, reason); + { + let ecomps = world.write_component::(); + let ecomp = ecomps.get(player).unwrap(); + + info!("Disconnecting player {}: {}", ecomp.display_name, reason); + + // Decrement player count + let player_count = world.fetch_mut::>(); + player_count.0.fetch_sub(1, Ordering::SeqCst); + + // Trigger disconnect event + let event = PlayerDisconnectEvent { + player, + uuid: ecomp.uuid.clone(), + reason, + }; + world + .fetch_mut::>() + .single_write(event); } world.delete_entity(player).unwrap(); - - // Decrement player count - let player_count = world.fetch_mut::>(); - player_count.0.fetch_sub(1, Ordering::SeqCst); } #[cfg(test)] diff --git a/server/src/player.rs b/server/src/player.rs index cecf44e6e..5ca759c06 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -15,7 +15,7 @@ use specs::{ use feather_core::network::cast_packet; use feather_core::network::packet::implementation::{ - ChunkData, PlayerInfo, PlayerInfoAction, PlayerLook, PlayerPosition, + ChunkData, DestroyEntities, PlayerInfo, PlayerInfoAction, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, SpawnPlayer, }; use feather_core::network::packet::{Packet, PacketType}; @@ -28,6 +28,14 @@ use crate::entity::{broadcast_entity_movement, EntityComponent, PlayerComponent} use crate::joinhandler::{PlayerJoinEvent, SPAWN_POSITION}; use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue, PlayerPreJoinEvent}; use feather_core::entitymeta::{EntityMetadata, MetaEntry}; +use uuid::Uuid; + +/// Event which is called when a player disconnected. +pub struct PlayerDisconnectEvent { + pub player: Entity, + pub reason: String, + pub uuid: Uuid, +} /// System for handling player movement /// packets. @@ -386,6 +394,51 @@ fn get_player_initialization_packets( (player_info, spawn_player) } +/// System for broadcasting when a player disconnects. +pub struct DisconnectBroadcastSystem { + reader: Option>, +} + +impl DisconnectBroadcastSystem { + pub fn new() -> Self { + Self { reader: None } + } +} + +impl<'a> System<'a> for DisconnectBroadcastSystem { + type SystemData = ( + Read<'a, EventChannel>, + ReadStorage<'a, NetworkComponent>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (events, net_comps) = data; + + for event in events.read(&mut self.reader.as_mut().unwrap()) { + // Broadcast disconnect. + let player_info = PlayerInfo::new(PlayerInfoAction::RemovePlayer, event.uuid.clone()); + + let destroy_entities = DestroyEntities::new(vec![event.player.id() as i32]); + + for net in net_comps.join() { + send_packet_to_player(net, player_info.clone()); + send_packet_to_player(net, destroy_entities.clone()); + } + } + } + + fn setup(&mut self, world: &mut World) { + use specs::SystemData; + Self::SystemData::setup(world); + + self.reader = Some( + world + .fetch_mut::>() + .register_reader(), + ); + } +} + fn send_chunk_data(chunk: &Chunk, net: &NetworkComponent) { let packet = ChunkData::new(chunk.clone()); send_packet_to_player(net, packet); From bc75836eb7fa946045ff88d77c72efd973f5b071 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 19:21:04 -0600 Subject: [PATCH 29/30] Use bitwise and in chunk_relative_pos() - fixes #21 --- core/src/network/mctypes.rs | 16 ++++++++-------- core/src/world/mod.rs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/network/mctypes.rs b/core/src/network/mctypes.rs index 71649fbed..bdebc290c 100644 --- a/core/src/network/mctypes.rs +++ b/core/src/network/mctypes.rs @@ -1,4 +1,4 @@ -use crate::bytebuf::{BufMutAlloc, ByteBuf}; +use crate::bytebuf::{BufMutAlloc, BufResulted, ByteBuf}; use crate::prelude::*; use crate::world::BlockPosition; use bytes::Buf; @@ -110,7 +110,7 @@ impl McTypeRead for T { if self.remaining() == 0 { return Err(()); } - let read = self.get_u8(); + let read = self.read_u8()?; let value = read & 0b01111111; result |= (value as i32) << (7 * num_read); @@ -149,16 +149,16 @@ impl McTypeRead for T { if self.remaining() < 8 { return Err(()); } - let val = self.get_u64_be(); - let x = val as i32 >> 38; - let y = (val as i32 >> 26) & 0xFFF; - let z = (val as i32) << 38 >> 38; + let val = self.read_i64_be()?; + let x = val >> 38; + let y = (val >> 26) & 0xFFF; + let z = val << 38 >> 38; - Ok(BlockPosition::new(x, y, z)) + Ok(BlockPosition::new(x as i32, y as i32, z as i32)) } fn read_bool(&mut self) -> Result { - let byte = self.get_u8(); + let byte = self.read_u8()?; match byte { 0 => Ok(false), 1 => Ok(true), diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 00c1da73a..febe7fd18 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -121,9 +121,9 @@ impl Default for ChunkMap { fn chunk_relative_pos(block_pos: BlockPosition) -> (usize, usize, usize) { ( - (block_pos.x % 16) as usize, + block_pos.x as usize & 0xf, block_pos.y as usize, - (block_pos.z % 16) as usize, + block_pos.z as usize & 0xf, ) } From b5dfb33bb6191cc07b6c70385265d055d887143e Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jul 2019 19:25:27 -0600 Subject: [PATCH 30/30] Fix compiler warnings --- server/src/entity.rs | 8 ++------ server/src/io/initialhandler.rs | 3 +-- server/src/io/worker.rs | 5 +---- server/src/joinhandler.rs | 7 +++---- server/src/network.rs | 5 ++--- server/src/player.rs | 8 +++++--- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/server/src/entity.rs b/server/src/entity.rs index a8688c728..5e938a684 100644 --- a/server/src/entity.rs +++ b/server/src/entity.rs @@ -3,20 +3,16 @@ //! provide entity-specific components and systems. use specs::storage::BTreeStorage; -use specs::{Component, Entities, Entity, LazyUpdate, ReadStorage, VecStorage}; +use specs::{Component, Entities, Entity, ReadStorage, VecStorage}; use uuid::Uuid; use feather_core::network::packet::implementation::{ EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, }; -use feather_core::network::packet::PacketType; use feather_core::world::Position; use feather_core::Gamemode; -use crate::disconnect_player; -use crate::network::{ - send_packet_to_all_players, send_packet_to_player, NetworkComponent, PacketQueue, -}; +use crate::network::{send_packet_to_all_players, NetworkComponent}; pub struct PlayerComponent { pub profile_properties: Vec, diff --git a/server/src/io/initialhandler.rs b/server/src/io/initialhandler.rs index 63afbad7b..79e2fd570 100644 --- a/server/src/io/initialhandler.rs +++ b/server/src/io/initialhandler.rs @@ -30,7 +30,6 @@ use feather_core::network::packet::implementation::{ use feather_core::network::packet::{Packet, PacketType}; use crate::config::Config; -use crate::io::NewClientInfo; use crate::{PlayerCount, PROTOCOL_VERSION, SERVER_VERSION}; /// The key used for symmetric encryption. @@ -336,7 +335,7 @@ fn decrypt_using_rsa(data: &[u8], key: &Rsa) -> Result, Error> let len = key .private_decrypt(data, &mut buf, Padding::PKCS1) - .map_err(|e| Error::BadEncryption)?; + .map_err(|_| Error::BadEncryption)?; buf.truncate(len); diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 86afb4fc0..7aaa2762a 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -8,17 +8,14 @@ use mio::Event; use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; use feather_core::bytebuf::{BufMutAlloc, ByteBuf}; -use feather_core::network::packet::{PacketDirection, PacketType}; +use feather_core::network::packet::PacketDirection; use feather_core::network::serialize::ConnectionIOManager; use crate::config::Config; use crate::io::initialhandler::{Action, InitialHandler}; use crate::PlayerCount; -use super::initialhandler as ih; use super::*; -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::PlayerDigging; // The token used to listen on the channel receiving messages from the listener thread const LISTENER_TOKEN: Token = Token(0); diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs index abcd420e7..4121836e5 100644 --- a/server/src/joinhandler.rs +++ b/server/src/joinhandler.rs @@ -10,18 +10,17 @@ use std::sync::Arc; use shrev::EventChannel; use specs::{ - Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, ReaderId, - System, World, Write, WriteStorage, + Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadStorage, System, + Write, WriteStorage, }; use feather_core::network::packet::implementation::{ JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, }; -use feather_core::network::packet::PacketType::Player; use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; use feather_core::{Difficulty, Dimension, Gamemode}; -use crate::chunkclient::{ChunkLoadEvent, ChunkWorkerHandle}; +use crate::chunkclient::ChunkWorkerHandle; use crate::config::Config; use crate::network::NetworkComponent; use crate::player::ChunkPendingComponent; diff --git a/server/src/network.rs b/server/src/network.rs index 4c5e484d8..19c378b19 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -7,11 +7,10 @@ use specs::{ WorldExt, Write, WriteStorage, }; -use feather_core::entitymeta::{EntityMetadata, MetaEntry}; use feather_core::network::packet::{implementation::*, Packet, PacketType}; -use crate::entity::{EntityComponent, PlayerComponent}; -use crate::io::{NetworkIoManager, NewClientInfo, ServerToListenerMessage, ServerToWorkerMessage}; +use crate::entity::PlayerComponent; +use crate::io::{NetworkIoManager, ServerToListenerMessage, ServerToWorkerMessage}; use crate::joinhandler::JoinHandlerComponent; use crate::prelude::*; use crate::{disconnect_player_without_packet, TickCount}; diff --git a/server/src/player.rs b/server/src/player.rs index 5ca759c06..4c010644c 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -177,7 +177,7 @@ impl<'a> System<'a> for PlayerInitSystem { profile_properties: event.profile_properties.clone(), gamemode: Gamemode::Creative, }; - player_comps.insert(event.player, player_comp); + player_comps.insert(event.player, player_comp).unwrap(); let entity_comp = EntityComponent { uuid: event.uuid.clone(), @@ -185,12 +185,14 @@ impl<'a> System<'a> for PlayerInitSystem { position: SPAWN_POSITION, on_ground: true, }; - entity_comps.insert(event.player, entity_comp); + entity_comps.insert(event.player, entity_comp).unwrap(); let chunk_pending_comp = ChunkPendingComponent { pending: HashSet::new(), }; - chunk_pending_comps.insert(event.player, chunk_pending_comp); + chunk_pending_comps + .insert(event.player, chunk_pending_comp) + .unwrap(); } }