From 34d211d11777dd6e942967d14c6e4f96ff28d922 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Wed, 10 Apr 2019 18:23:27 +0100 Subject: [PATCH 01/16] Integrated Sphynx Former-commit-id: 5d96983a385bb77a2876aa7439158252b7e2f0fc --- chat-cli/src/main.rs | 7 +- client/src/error.rs | 1 + client/src/lib.rs | 55 ++++++++------- common/Cargo.toml | 2 + common/src/comp/character.rs | 26 +++++-- common/src/comp/mod.rs | 21 ++---- common/src/comp/phys.rs | 21 ++---- common/src/comp/player.rs | 18 +++++ common/src/comp/uid.rs | 84 ---------------------- common/src/comp/util.rs | 12 ---- common/src/lib.rs | 2 +- common/src/msg/client.rs | 15 ++-- common/src/msg/ecs_packet.rs | 29 ++++++++ common/src/msg/mod.rs | 6 +- common/src/msg/server.rs | 19 ++--- common/src/net/post.rs | 39 ++++++++++ common/src/state.rs | 126 +++++++++++++-------------------- server-cli/src/main.rs | 6 +- server/src/client.rs | 25 +++++-- server/src/lib.rs | 133 +++++++++++++++++------------------ voxygen/src/menu/main/mod.rs | 9 ++- voxygen/src/scene/mod.rs | 3 +- voxygen/src/session.rs | 2 + 23 files changed, 317 insertions(+), 344 deletions(-) create mode 100644 common/src/comp/player.rs delete mode 100644 common/src/comp/uid.rs delete mode 100644 common/src/comp/util.rs create mode 100644 common/src/msg/ecs_packet.rs diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index fa93a3df36..deb5cb126c 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -1,7 +1,10 @@ use std::time::Duration; use log::info; use client::{Input, Client, Event}; -use common::clock::Clock; +use common::{ + comp, + clock::Clock, +}; const FPS: u64 = 60; @@ -15,7 +18,7 @@ fn main() { let mut clock = Clock::new(); // Create client - let mut client = Client::new(([127, 0, 0, 1], 59003)) + let mut client = Client::new(([127, 0, 0, 1], 59003), comp::Player::new("test".to_string()), None) .expect("Failed to create client instance"); client.send_chat("Hello!".to_string()); diff --git a/client/src/error.rs b/client/src/error.rs index e702a67b0f..b4b3ce4742 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -3,6 +3,7 @@ use common::net::PostError; #[derive(Debug)] pub enum Error { Network(PostError), + ServerWentMad, ServerTimeout, ServerShutdown, Other(String), diff --git a/client/src/lib.rs b/client/src/lib.rs index 0e364d9701..0fcccc1e27 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -50,11 +50,31 @@ pub struct Client { impl Client { /// Create a new `Client`. #[allow(dead_code)] - pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> { - let state = State::new(); + pub fn new<A: Into<SocketAddr>>( + addr: A, + player: comp::Player, + character: Option<comp::Character>, + ) -> Result<Self, Error> { let mut postbox = PostBox::to_server(addr)?; + // Send connection request + postbox.send(ClientMsg::Connect { + player, + character, + }); + + // Wait for handshake from server + let (state, player) = match postbox.next_message() { + Some(ServerMsg::Handshake { ecs_state, player_entity }) => { + println!("STATE PACKAGE! {:?}", ecs_state); + let mut state = State::from_state_package(ecs_state); + let player_entity = state.ecs().entity_from_uid(player_entity); + (state, player_entity) + }, + _ => return Err(Error::ServerWentMad), + }; + Ok(Self { thread_pool: threadpool::Builder::new() .thread_name("veloren-worker".into()) @@ -65,7 +85,7 @@ impl Client { tick: 0, state, - player: None, + player, // Testing world: World::new(), @@ -85,12 +105,6 @@ impl Client { self } - // TODO: Get rid of this - pub fn load_chunk(&mut self, pos: Vec3<i32>) { - self.state.terrain_mut().insert(pos, self.world.generate_chunk(pos)); - self.state.changes_mut().new_chunks.push(pos); - } - /// Get a reference to the client's game state. #[allow(dead_code)] pub fn state(&self) -> &State { &self.state } @@ -187,28 +201,15 @@ impl Client { for msg in new_msgs { match msg { + ServerMsg::Handshake { .. } => return Err(Error::ServerWentMad), ServerMsg::Shutdown => return Err(Error::ServerShutdown), ServerMsg::Ping => self.postbox.send(ClientMsg::Pong), ServerMsg::Pong => {}, ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), - ServerMsg::SetPlayerEntity(uid) => { - let ecs_entity = self.state - .get_entity(uid) - .unwrap_or_else(|| self.state.build_uid_entity_with_uid(uid).build()); - - self.player = Some(ecs_entity); - }, - ServerMsg::EntityPhysics { uid, pos, vel, dir } => { - let ecs_entity = self.state - .get_entity(uid) - .unwrap_or_else(|| self.state.build_uid_entity_with_uid(uid).build()); - - self.state.write_component(ecs_entity, pos); - self.state.write_component(ecs_entity, vel); - self.state.write_component(ecs_entity, dir); - }, - ServerMsg::EntityDeleted(uid) => { - self.state.delete_entity(uid); + ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here! + ServerMsg::EcsSync(sync_package) => { + println!("SYNC PACKAGE! {:?}", sync_package); + self.state.ecs_mut().sync_with_package(sync_package) }, } } diff --git a/common/Cargo.toml b/common/Cargo.toml index 3fa8fd33a3..75c032bf18 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc edition = "2018" [dependencies] +sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"] } + specs = { version = "0.14", features = ["serde"] } shred = { version = "0.7", features = ["nightly"] } vek = { version = "0.9", features = ["serde"] } diff --git a/common/src/comp/character.rs b/common/src/comp/character.rs index 090f0e047a..02cef3eff6 100644 --- a/common/src/comp/character.rs +++ b/common/src/comp/character.rs @@ -1,9 +1,8 @@ -// Library -use specs::{Component, VecStorage}; +use specs::{Component, VecStorage, FlaggedStorage}; use vek::*; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -enum Race { +pub enum Race { Danari, Dwarf, Elf, @@ -13,7 +12,7 @@ enum Race { } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct Gender { +pub enum Gender { Female, Male, Unspecified, @@ -30,6 +29,21 @@ pub struct Character { feet: (), } -impl Component for Character { - type Storage = VecStorage<Self>; +impl Character { + // TODO: Remove this + pub fn test() -> Self { + Self { + race: Race::Human, + gender: Gender::Unspecified, + head: (), + chest: (), + belt: (), + arms: (), + feet: (), + } + } +} + +impl Component for Character { + type Storage = FlaggedStorage<Self, VecStorage<Self>>; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 776a67485c..f5fa9254b7 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,20 +1,7 @@ +pub mod character; +pub mod player; pub mod phys; -pub mod uid; -pub mod util; // Reexports -pub use uid::{Uid, UidAllocator}; - -use specs::World as EcsWorld; - -pub fn register_local_components(ecs_world: &mut EcsWorld) { - ecs_world.register::<Uid>(); - ecs_world.add_resource(UidAllocator::new()); - - ecs_world.register::<util::New>(); - - ecs_world.register::<phys::Pos>(); - ecs_world.register::<phys::Vel>(); - ecs_world.register::<phys::Dir>(); - ecs_world.register::<phys::UpdateKind>(); -} +pub use character::Character; +pub use player::Player; diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 3db813276d..20e2b59192 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -1,5 +1,4 @@ -// Library -use specs::{Component, VecStorage}; +use specs::{Component, VecStorage, FlaggedStorage}; use vek::*; // Pos @@ -8,7 +7,7 @@ use vek::*; pub struct Pos(pub Vec3<f32>); impl Component for Pos { - type Storage = VecStorage<Self>; + type Storage = FlaggedStorage<Self, VecStorage<Self>>; } // Vel @@ -17,7 +16,7 @@ impl Component for Pos { pub struct Vel(pub Vec3<f32>); impl Component for Vel { - type Storage = VecStorage<Self>; + type Storage = FlaggedStorage<Self, VecStorage<Self>>; } // Dir @@ -26,17 +25,5 @@ impl Component for Vel { pub struct Dir(pub Vec3<f32>); impl Component for Dir { - type Storage = VecStorage<Self>; -} - -// UpdateKind - -#[derive(Copy, Clone, Debug)] -pub enum UpdateKind { - Passive, - Force, -} - -impl Component for UpdateKind { - type Storage = VecStorage<Self>; + type Storage = FlaggedStorage<Self, VecStorage<Self>>; } diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs new file mode 100644 index 0000000000..46f1a048a4 --- /dev/null +++ b/common/src/comp/player.rs @@ -0,0 +1,18 @@ +use specs::{Component, VecStorage, FlaggedStorage}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Player { + pub alias: String, +} + +impl Player { + pub fn new(alias: String) -> Self { + Self { + alias, + } + } +} + +impl Component for Player { + type Storage = FlaggedStorage<Self, VecStorage<Self>>; +} diff --git a/common/src/comp/uid.rs b/common/src/comp/uid.rs deleted file mode 100644 index 6ea657dada..0000000000 --- a/common/src/comp/uid.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{ - collections::HashMap, - ops::Range, - u64, - fmt, -}; -use specs::{ - saveload::{Marker, MarkerAllocator}, - world::EntitiesRes, - Component, - VecStorage, - Entity, - Join, - ReadStorage, -}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct Uid(pub u64); - -impl Into<u64> for Uid { - fn into(self) -> u64 { - self.0 - } -} - -impl fmt::Display for Uid { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Component for Uid { - type Storage = VecStorage<Self>; -} - -impl Marker for Uid { - type Identifier = u64; - type Allocator = UidAllocator; - - fn id(&self) -> u64 { self.0 } - - fn update(&mut self, update: Self) { - assert_eq!(self.0, update.0); - } -} - -pub struct UidAllocator { - pub(crate) range: Range<u64>, - pub(crate) mapping: HashMap<u64, Entity>, -} - -impl UidAllocator { - pub fn new() -> Self { - Self { - range: 0..u64::MAX, - mapping: HashMap::new(), - } - } -} - -impl MarkerAllocator<Uid> for UidAllocator { - fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid { - let id = id.unwrap_or_else(|| { - self.range.next().expect(" - Id range must be effectively endless. - Somehow, you ran this program for longer than the lifetime of the universe. - It's probably time to stop playing and prepare for your imminent extinction. - ") - }); - self.mapping.insert(id, entity); - Uid(id) - } - - fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> { - self.mapping.get(&id).cloned() - } - - fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) { - self.mapping = (&*entities, storage) - .join() - .map(|(e, m)| (m.id(), e)) - .collect(); - } -} diff --git a/common/src/comp/util.rs b/common/src/comp/util.rs deleted file mode 100644 index 0ea2d35ebc..0000000000 --- a/common/src/comp/util.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Library -use specs::{Component, NullStorage}; -use vek::*; - -// Pos - -#[derive(Copy, Clone, Debug, Default)] -pub struct New; - -impl Component for New { - type Storage = NullStorage<Self>; -} diff --git a/common/src/lib.rs b/common/src/lib.rs index 9fc68777e4..72bcade3f8 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(euclidean_division, duration_float, try_from, trait_alias)] +#![feature(euclidean_division, duration_float, trait_alias)] #[macro_use] extern crate serde_derive; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 149cd07ad4..e8b59414ed 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,17 +1,18 @@ -use crate::comp::{ - Uid, - phys, -}; +use crate::comp; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ClientMsg { + Connect { + player: comp::Player, + character: Option<comp::Character>, + }, Ping, Pong, Chat(String), PlayerPhysics { - pos: phys::Pos, - vel: phys::Vel, - dir: phys::Dir, + pos: comp::phys::Pos, + vel: comp::phys::Vel, + dir: comp::phys::Dir, }, Disconnect, } diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs new file mode 100644 index 0000000000..a3b4a9e463 --- /dev/null +++ b/common/src/msg/ecs_packet.rs @@ -0,0 +1,29 @@ +use std::marker::PhantomData; +use serde_derive::{Serialize, Deserialize}; +use crate::comp; + +// Automatically derive From<T> for Packet for each variant Packet::T(T) +sphynx::sum_type! { + #[derive(Clone, Debug, Serialize, Deserialize)] + pub enum EcsPacket { + Pos(comp::phys::Pos), + Vel(comp::phys::Vel), + Dir(comp::phys::Dir), + Character(comp::Character), + Player(comp::Player), + } +} +// Automatically derive From<T> for Phantom for each variant Phantom::T(PhantomData<T>) +sphynx::sum_type! { + #[derive(Clone, Debug, Serialize, Deserialize)] + pub enum EcsPhantom { + Pos(PhantomData<comp::phys::Pos>), + Vel(PhantomData<comp::phys::Vel>), + Dir(PhantomData<comp::phys::Dir>), + Character(PhantomData<comp::Character>), + Player(PhantomData<comp::Player>), + } +} +impl sphynx::Packet for EcsPacket { + type Phantom = EcsPhantom; +} diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index ee18da8f4d..b38939eeb2 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -1,6 +1,8 @@ +pub mod ecs_packet; pub mod server; pub mod client; // Reexports -pub use server::ServerMsg; -pub use client::ClientMsg; +pub use self::server::ServerMsg; +pub use self::client::ClientMsg; +pub use self::ecs_packet::EcsPacket; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 871215138e..21a21f506e 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,20 +1,15 @@ -use crate::comp::{ - Uid, - phys, -}; +use super::EcsPacket; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ServerMsg { + Handshake { + ecs_state: sphynx::StatePackage<EcsPacket>, + player_entity: u64, + }, Shutdown, Ping, Pong, Chat(String), - SetPlayerEntity(Uid), - EntityPhysics { - uid: Uid, - pos: phys::Pos, - vel: phys::Vel, - dir: phys::Dir, - }, - EntityDeleted(Uid), + SetPlayerEntity(u64), + EcsSync(sphynx::SyncPackage<EcsPacket>), } diff --git a/common/src/net/post.rs b/common/src/net/post.rs index b9160ec35b..afd3d8956d 100644 --- a/common/src/net/post.rs +++ b/common/src/net/post.rs @@ -251,6 +251,45 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> { let _ = self.send_tx.send(data); } + // TODO: This method is super messy + pub fn next_message(&mut self) -> Option<R> { + if self.err.is_some() { + return None; + } + + loop { + let mut events = Events::with_capacity(10); + if let Err(err) = self.poll.poll( + &mut events, + Some(Duration::new(0, 0)), + ) { + self.err = Some(err.into()); + return None; + } + + for event in events { + match event.token() { + // Keep reading new messages from the channel + RECV_TOK => loop { + match self.recv_rx.try_recv() { + Ok(Ok(msg)) => return Some(msg), + Err(TryRecvError::Empty) => break, + Err(err) => { + self.err = Some(err.into()); + return None; + }, + Ok(Err(err)) => { + self.err = Some(err); + return None; + }, + } + }, + tok => panic!("Unexpected event token '{:?}'", tok), + } + } + } + } + pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> { let mut msgs = VecDeque::new(); diff --git a/common/src/state.rs b/common/src/state.rs index b395dd35de..1e7f9a076d 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -6,18 +6,19 @@ use specs::{ DispatcherBuilder, EntityBuilder as EcsEntityBuilder, Entity as EcsEntity, - World as EcsWorld, storage::{ Storage as EcsStorage, MaskedStorage as EcsMaskedStorage, }, saveload::{MarkedBuilder, MarkerAllocator}, }; +use sphynx; use vek::*; use crate::{ comp, sys, terrain::TerrainMap, + msg::EcsPacket, }; /// How much faster should an in-game day be compared to a real day? @@ -59,95 +60,74 @@ impl Changes { /// A type used to represent game state stored on both the client and the server. This includes /// things like entity components, terrain data, and global state like weather, time of day, etc. pub struct State { - ecs_world: EcsWorld, + ecs: sphynx::World<EcsPacket>, changes: Changes, } impl State { /// Create a new `State`. pub fn new() -> Self { - let mut ecs_world = EcsWorld::new(); - - // Register resources used by the ECS - ecs_world.add_resource(TimeOfDay(0.0)); - ecs_world.add_resource(Time(0.0)); - ecs_world.add_resource(DeltaTime(0.0)); - ecs_world.add_resource(TerrainMap::new()); - - // Register common components with the state - comp::register_local_components(&mut ecs_world); - Self { - ecs_world, + ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world), changes: Changes::default(), } } + /// Create a new `State` from an ECS state package + pub fn from_state_package(state_package: sphynx::StatePackage<EcsPacket>) -> Self { + Self { + ecs: sphynx::World::from_state_package(specs::World::new(), Self::setup_sphynx_world, state_package), + changes: Changes::default(), + } + } + + // Create a new Sphynx ECS world + fn setup_sphynx_world(ecs: &mut sphynx::World<EcsPacket>) { + // Register synced components + ecs.register_synced::<comp::phys::Pos>(); + ecs.register_synced::<comp::phys::Vel>(); + ecs.register_synced::<comp::phys::Dir>(); + ecs.register_synced::<comp::Character>(); + ecs.register_synced::<comp::Player>(); + + // Register resources used by the ECS + ecs.internal_mut().add_resource(TimeOfDay(0.0)); + ecs.internal_mut().add_resource(Time(0.0)); + ecs.internal_mut().add_resource(DeltaTime(0.0)); + ecs.internal_mut().add_resource(TerrainMap::new()); + } + /// Register a component with the state's ECS pub fn with_component<T: Component>(mut self) -> Self where <T as Component>::Storage: Default { - self.ecs_world.register::<T>(); + self.ecs.internal_mut().register::<T>(); self } - /// Build a new entity with a generated UID - pub fn build_uid_entity(&mut self) -> EcsEntityBuilder { - self.ecs_world.create_entity() - .with(comp::util::New) - .marked::<comp::Uid>() - } - - /// Build an entity with a specific UID - pub fn build_uid_entity_with_uid(&mut self, uid: comp::Uid) -> EcsEntityBuilder { - let builder = self.build_uid_entity(); - - builder.world - .write_resource::<comp::UidAllocator>() - .allocate(builder.entity, Some(uid.into())); - - builder - } - - /// Get an entity from its UID, if it exists - pub fn get_entity(&self, uid: comp::Uid) -> Option<EcsEntity> { - // Find the ECS entity from its UID - self.ecs_world - .read_resource::<comp::UidAllocator>() - .retrieve_entity_internal(uid.into()) - } - - /// Delete an entity from the state's ECS, if it exists - pub fn delete_entity(&mut self, uid: comp::Uid) { - // Find the ECS entity from its UID - let ecs_entity = self.ecs_world - .read_resource::<comp::UidAllocator>() - .retrieve_entity_internal(uid.into()); - - // Delete the ECS entity, if it exists - if let Some(ecs_entity) = ecs_entity { - let _ = self.ecs_world.delete_entity(ecs_entity); - } - } - /// Write a component attributed to a particular entity pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: C) { - let _ = self.ecs_world.write_storage().insert(entity, comp); + let _ = self.ecs.internal_mut().write_storage().insert(entity, comp); + } + + /// Read a component attributed to a particular entity + pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> { + self.ecs.internal().read_storage().get(entity).cloned() } /// Get a read-only reference to the storage of a particular component type pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> { - self.ecs_world.read_storage::<C>() + self.ecs.internal().read_storage::<C>() } /// Get a reference to the internal ECS world - pub fn ecs_world(&self) -> &EcsWorld { - &self.ecs_world + pub fn ecs(&self) -> &sphynx::World<EcsPacket> { + &self.ecs } /// Get a mutable reference to the internal ECS world - pub fn ecs_world_mut(&mut self) -> &mut EcsWorld { - &mut self.ecs_world + pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsPacket> { + &mut self.ecs } /// Get a reference to the `Changes` structure of the state. This contains @@ -156,54 +136,46 @@ impl State { &self.changes } - // TODO: Get rid of this since it shouldn't be needed - pub fn changes_mut(&mut self) -> &mut Changes { - &mut self.changes - } - /// Get the current in-game time of day. /// /// Note that this should not be used for physics, animations or other such localised timings. pub fn get_time_of_day(&self) -> f64 { - self.ecs_world.read_resource::<TimeOfDay>().0 + self.ecs.internal().read_resource::<TimeOfDay>().0 } /// Get the current in-game time. /// /// Note that this does not correspond to the time of day. pub fn get_time(&self) -> f64 { - self.ecs_world.read_resource::<Time>().0 + self.ecs.internal().read_resource::<Time>().0 } /// Get a reference to this state's terrain. pub fn terrain(&self) -> Fetch<TerrainMap> { - self.ecs_world.read_resource::<TerrainMap>() + self.ecs.internal().read_resource::<TerrainMap>() } // TODO: Get rid of this since it shouldn't be needed pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> { - self.ecs_world.write_resource::<TerrainMap>() + self.ecs.internal_mut().write_resource::<TerrainMap>() } /// Execute a single tick, simulating the game state by the given duration. pub fn tick(&mut self, dt: Duration) { - // First, wipe all temporary marker components - self.ecs_world.write_storage::<comp::util::New>().clear(); - // Change the time accordingly - self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR; - self.ecs_world.write_resource::<Time>().0 += dt.as_secs_f64(); + self.ecs.internal_mut().write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR; + self.ecs.internal_mut().write_resource::<Time>().0 += dt.as_secs_f64(); // Run systems to update the world - self.ecs_world.write_resource::<DeltaTime>().0 = dt.as_secs_f64(); + self.ecs.internal_mut().write_resource::<DeltaTime>().0 = dt.as_secs_f64(); // Create and run dispatcher for ecs systems let mut dispatch_builder = DispatcherBuilder::new(); sys::add_local_systems(&mut dispatch_builder); // This dispatches all the systems in parallel - dispatch_builder.build().dispatch(&self.ecs_world.res); + dispatch_builder.build().dispatch(&self.ecs.internal_mut().res); - self.ecs_world.maintain(); + self.ecs.internal_mut().maintain(); } /// Clean up the state after a tick diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 244f324a3d..1070bbf482 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -24,9 +24,9 @@ fn main() { for event in events { match event { - Event::ClientConnected { ecs_entity } => info!("Client connected!"), - Event::ClientDisconnected { ecs_entity } => info!("Client disconnected!"), - Event::Chat { ecs_entity, msg } => info!("[Client] {}", msg), + Event::ClientConnected { entity } => info!("Client connected!"), + Event::ClientDisconnected { entity } => info!("Client disconnected!"), + Event::Chat { entity, msg } => info!("[Client] {}", msg), } } diff --git a/server/src/client.rs b/server/src/client.rs index 8030fb03e5..fed88d9ae4 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -6,12 +6,25 @@ use common::{ }; use crate::Error; +#[derive(PartialEq)] +pub enum ClientState { + Connecting, + Connected, +} + pub struct Client { - pub ecs_entity: EcsEntity, + pub state: ClientState, + pub entity: EcsEntity, pub postbox: PostBox<ServerMsg, ClientMsg>, pub last_ping: f64, } +impl Client { + pub fn notify(&mut self, msg: ServerMsg) { + self.postbox.send(msg); + } +} + pub struct Clients { clients: Vec<Client>, } @@ -31,15 +44,17 @@ impl Clients { self.clients.drain_filter(f); } - pub fn notify_all(&mut self, msg: ServerMsg) { + pub fn notify_connected(&mut self, msg: ServerMsg) { for client in &mut self.clients { - client.postbox.send(msg.clone()); + if client.state == ClientState::Connected { + client.postbox.send(msg.clone()); + } } } - pub fn notify_all_except(&mut self, ecs_entity: EcsEntity, msg: ServerMsg) { + pub fn notify_connected_except(&mut self, entity: EcsEntity, msg: ServerMsg) { for client in &mut self.clients { - if client.ecs_entity != ecs_entity { + if client.entity != entity && client.state == ClientState::Connected { client.postbox.send(msg.clone()); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 867d4b1b6b..937c70368c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -30,6 +30,7 @@ use common::{ }; use world::World; use crate::client::{ + ClientState, Client, Clients, }; @@ -38,13 +39,13 @@ const CLIENT_TIMEOUT: f64 = 5.0; // Seconds pub enum Event { ClientConnected { - ecs_entity: EcsEntity, + entity: EcsEntity, }, ClientDisconnected { - ecs_entity: EcsEntity, + entity: EcsEntity, }, Chat { - ecs_entity: EcsEntity, + entity: EcsEntity, msg: String, }, } @@ -77,15 +78,6 @@ impl Server { #[allow(dead_code)] pub fn state_mut(&mut self) -> &mut State { &mut self.state } - /// Build a new player with a generated UID - pub fn build_player(&mut self) -> EcsEntityBuilder { - self.state.build_uid_entity() - .with(comp::phys::Pos(Vec3::zero())) - .with(comp::phys::Vel(Vec3::zero())) - .with(comp::phys::Dir(Vec3::unit_y())) - .with(comp::phys::UpdateKind::Passive) - } - /// Get a reference to the server's world. #[allow(dead_code)] pub fn world(&self) -> &World { &self.world } @@ -146,23 +138,20 @@ impl Server { let mut frontend_events = Vec::new(); for mut postbox in self.postoffice.new_connections() { - let ecs_entity = self.build_player() - // When the player is first created, force a physics notification to everyone - // including themselves. - .with(comp::phys::UpdateKind::Force) + let entity = self.state + .ecs_mut() + .create_entity_synced() .build(); - let uid = self.state.read_storage().get(ecs_entity).cloned().unwrap(); - - postbox.send(ServerMsg::SetPlayerEntity(uid)); self.clients.add(Client { - ecs_entity, + state: ClientState::Connecting, + entity, postbox, last_ping: self.state.get_time(), }); frontend_events.push(Event::ClientConnected { - ecs_entity, + entity, }); } @@ -178,7 +167,7 @@ impl Server { let mut disconnected_clients = Vec::new(); self.clients.remove_if(|client| { - let mut disconnected = false; + let mut disconnect = false; let new_msgs = client.postbox.new_messages(); // Update client ping @@ -187,30 +176,56 @@ impl Server { // Process incoming messages for msg in new_msgs { - match msg { - ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), - ClientMsg::Pong => {}, - ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)), - ClientMsg::PlayerPhysics { pos, vel, dir } => { - state.write_component(client.ecs_entity, pos); - state.write_component(client.ecs_entity, vel); - state.write_component(client.ecs_entity, dir); + match client.state { + ClientState::Connecting => match msg { + ClientMsg::Connect { player, character } => { + + // Write client components + state.write_component(client.entity, player); + if let Some(character) = character { + state.write_component(client.entity, character); + } + + client.state = ClientState::Connected; + + // Return a handshake with the state of the current world + client.notify(ServerMsg::Handshake { + ecs_state: state.ecs().gen_state_package(), + player_entity: state + .ecs() + .uid_from_entity(client.entity) + .unwrap() + .into(), + }); + }, + _ => disconnect = true, + }, + ClientState::Connected => match msg { + ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected + ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), + ClientMsg::Pong => {}, + ClientMsg::Chat(msg) => new_chat_msgs.push((client.entity, msg)), + ClientMsg::PlayerPhysics { pos, vel, dir } => { + state.write_component(client.entity, pos); + state.write_component(client.entity, vel); + state.write_component(client.entity, dir); + }, + ClientMsg::Disconnect => disconnect = true, }, - ClientMsg::Disconnect => disconnected = true, } } } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout client.postbox.error().is_some() // Postbox error { - disconnected = true; + disconnect = true; } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 { // Try pinging the client if the timeout is nearing client.postbox.send(ServerMsg::Ping); } - if disconnected { - disconnected_clients.push(client.ecs_entity); + if disconnect { + disconnected_clients.push(client.entity); true } else { false @@ -218,24 +233,30 @@ impl Server { }); // Handle new chat messages - for (ecs_entity, msg) in new_chat_msgs { - self.clients.notify_all(ServerMsg::Chat(msg.clone())); + for (entity, msg) in new_chat_msgs { + self.clients.notify_connected(ServerMsg::Chat(match state + .ecs() + .internal() + .read_storage::<comp::Player>() + .get(entity) + { + Some(player) => format!("[{}] {}", &player.alias, msg), + None => format!("[<anon>] {}", msg), + })); frontend_events.push(Event::Chat { - ecs_entity, + entity, msg, }); } // Handle client disconnects - for ecs_entity in disconnected_clients { - self.clients.notify_all(ServerMsg::EntityDeleted(state.read_storage().get(ecs_entity).cloned().unwrap())); + for entity in disconnected_clients { + state.ecs_mut().delete_entity_synced(entity); frontend_events.push(Event::ClientDisconnected { - ecs_entity, + entity, }); - - state.ecs_world_mut().delete_entity(ecs_entity); } Ok(frontend_events) @@ -243,36 +264,12 @@ impl Server { /// Sync client states with the most up to date information fn sync_clients(&mut self) { - for (entity, &uid, &pos, &vel, &dir, update_kind) in ( - &self.state.ecs_world().entities(), - &self.state.ecs_world().read_storage::<comp::Uid>(), - &self.state.ecs_world().read_storage::<comp::phys::Pos>(), - &self.state.ecs_world().read_storage::<comp::phys::Vel>(), - &self.state.ecs_world().read_storage::<comp::phys::Dir>(), - &mut self.state.ecs_world().write_storage::<comp::phys::UpdateKind>(), - ).join() { - let msg = ServerMsg::EntityPhysics { - uid, - pos, - vel, - dir, - }; - - // Sometimes we need to force updated (i.e: teleporting players). This involves sending - // everyone, including the player themselves, of their new physics information. - match update_kind { - comp::phys::UpdateKind::Force => self.clients.notify_all(msg), - comp::phys::UpdateKind::Passive => self.clients.notify_all_except(entity, msg), - } - - // Now that the update has occured, default to a passive update - *update_kind = comp::phys::UpdateKind::Passive; - } + self.clients.notify_connected(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package())); } } impl Drop for Server { fn drop(&mut self) { - self.clients.notify_all(ServerMsg::Shutdown); + self.clients.notify_connected(ServerMsg::Shutdown); } } diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 0dfe93880d..4b95bb6d46 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -6,7 +6,10 @@ use crate::{ GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::clock::Clock; +use common::{ + comp, + clock::Clock, +}; use std::time::Duration; use ui::{Event as MainMenuEvent, MainMenuUi}; use vek::*; @@ -67,12 +70,12 @@ impl PlayState for MainMenuState { Ok(mut socket_adders) => { while let Some(socket_addr) = socket_adders.next() { // TODO: handle error - match Client::new(socket_addr) { + match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test())) { Ok(client) => { return PlayStateResult::Push( Box::new(CharSelectionState::new( &mut global_state.window, - std::rc::Rc::new(std::cell::RefCell::new(client.with_test_state())) // <--- TODO: Remove this + std::rc::Rc::new(std::cell::RefCell::new(client)) // <--- TODO: Remove this )) ); } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 9513b6f602..e3e885b1ea 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -139,7 +139,8 @@ impl Scene { .player() .and_then(|ent| client .state() - .ecs_world() + .ecs() + .internal() .read_storage::<comp::phys::Pos>() .get(ent) .map(|pos| pos.0) diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 2d4f4911c5..d11a6dfa13 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -105,6 +105,7 @@ impl PlayState for SessionState { let mut clock = Clock::new(); // Load a few chunks TODO: Remove this + /* for x in -6..7 { for y in -6..7 { for z in -1..2 { @@ -112,6 +113,7 @@ impl PlayState for SessionState { } } } + */ // Game loop loop { From bd51eddd6223fa30b9909b5f7a044745f2223651 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Wed, 10 Apr 2019 22:51:14 +0100 Subject: [PATCH 02/16] Moved figure rendering to a better place Former-commit-id: 51ca948e131c20151c97f9f8454c80c47a9154b0 --- client/src/lib.rs | 15 +--- server/src/lib.rs | 3 + voxygen/src/anim/mod.rs | 2 +- voxygen/src/scene/figure.rs | 153 +++++++++++++++++++++++------------- voxygen/src/scene/mod.rs | 60 +++----------- voxygen/src/session.rs | 4 +- 6 files changed, 119 insertions(+), 118 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 0fcccc1e27..0101408ed4 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -67,7 +67,6 @@ impl Client { // Wait for handshake from server let (state, player) = match postbox.next_message() { Some(ServerMsg::Handshake { ecs_state, player_entity }) => { - println!("STATE PACKAGE! {:?}", ecs_state); let mut state = State::from_state_package(ecs_state); let player_entity = state.ecs().entity_from_uid(player_entity); (state, player_entity) @@ -99,12 +98,6 @@ impl Client { #[allow(dead_code)] pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool } - // TODO: Get rid of this - pub fn with_test_state(mut self) -> Self { - self.chunk = Some(self.world.generate_chunk(Vec3::zero())); - self - } - /// Get a reference to the client's game state. #[allow(dead_code)] pub fn state(&self) -> &State { &self.state } @@ -158,6 +151,9 @@ impl Client { const PLAYER_VELOCITY: f32 = 100.0; // TODO: Set acceleration instead self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); + if input.move_dir.magnitude() > 0.01 { + self.state.write_component(ecs_entity, comp::phys::Dir(input.move_dir.normalized().into())); + } } // Tick the client's LocalState (step 3) @@ -207,10 +203,7 @@ impl Client { ServerMsg::Pong => {}, ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here! - ServerMsg::EcsSync(sync_package) => { - println!("SYNC PACKAGE! {:?}", sync_package); - self.state.ecs_mut().sync_with_package(sync_package) - }, + ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package), } } } else if let Some(err) = self.postbox.error() { diff --git a/server/src/lib.rs b/server/src/lib.rs index 937c70368c..35946c683e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -182,6 +182,9 @@ impl Server { // Write client components state.write_component(client.entity, player); + state.write_component(client.entity, comp::phys::Pos(Vec3::zero())); + state.write_component(client.entity, comp::phys::Vel(Vec3::zero())); + state.write_component(client.entity, comp::phys::Dir(Vec3::unit_y())); if let Some(character) = character { state.write_component(client.entity, character); } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 088f56a4ca..be609f0d59 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -25,7 +25,7 @@ impl Bone { } } -pub trait Skeleton { +pub trait Skeleton: Send + Sync + 'static { fn compute_matrices(&self) -> [FigureBoneData; 16]; } diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 8418ab5850..e0c5ae6ba4 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -1,5 +1,14 @@ -use specs::{Component, VecStorage}; +use std::{ + collections::HashMap, + f32, +}; +use specs::{Entity as EcsEntity, Component, VecStorage, Join}; use vek::*; +use client::Client; +use common::{ + comp, + figure::Segment, +}; use crate::{ Error, render::{ @@ -12,42 +21,50 @@ use crate::{ FigureBoneData, FigureLocals, }, - anim::Skeleton, + anim::{ + Animation, + Skeleton, + character::{ + CharacterSkeleton, + RunAnimation, + }, + }, + mesh::Meshable, }; -pub struct Figure<S: Skeleton> { - // GPU data - model: Model<FigurePipeline>, - bone_consts: Consts<FigureBoneData>, - locals: Consts<FigureLocals>, - - // CPU data - bone_meshes: [Option<Mesh<FigurePipeline>>; 16], - pub skeleton: S, +pub struct Figures { + test_model: Model<FigurePipeline>, + states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>, } -impl<S: Skeleton> Figure<S> { - pub fn new( - renderer: &mut Renderer, - bone_meshes: [Option<Mesh<FigurePipeline>>; 16], - skeleton: S, - ) -> Result<Self, Error> { - let mut this = Self { - model: renderer.create_model(&Mesh::new())?, - bone_consts: renderer.create_consts(&skeleton.compute_matrices())?, - locals: renderer.create_consts(&[FigureLocals::default()])?, +impl Figures { + pub fn new(renderer: &mut Renderer) -> Self { + // TODO: Make a proper asset loading system + fn load_segment(filename: &'static str) -> Segment { + Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/voxel/").to_string() + filename)).unwrap()) + } - bone_meshes, - skeleton, - }; - this.update_model(renderer)?; - Ok(this) - } + let bone_meshes = [ + Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -6.5, -6.0))), + Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))), + Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), + Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), + Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), + Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), + Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), + Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), + Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))), + None, + None, + None, + None, + None, + None, + None, + ]; - pub fn update_model(&mut self, renderer: &mut Renderer) -> Result<(), Error> { let mut mesh = Mesh::new(); - - self.bone_meshes + bone_meshes .iter() .enumerate() .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) @@ -55,39 +72,69 @@ impl<S: Skeleton> Figure<S> { mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8)) }); - self.model = renderer.create_model(&mesh)?; - Ok(()) + Self { + test_model: renderer.create_model(&mesh).unwrap(), + states: HashMap::new(), + } } - pub fn update_skeleton(&mut self, renderer: &mut Renderer) -> Result<(), Error> { - renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())?; - Ok(()) + pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) { + let time = client.state().get_time(); + let ecs = client.state_mut().ecs_mut().internal_mut(); + for (entity, pos, dir, character) in ( + &ecs.entities(), + &ecs.read_storage::<comp::phys::Pos>(), + &ecs.read_storage::<comp::phys::Dir>(), + &ecs.read_storage::<comp::Character>(), + ).join() { + let state = self.states + .entry(entity) + .or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new())); + + state.update(renderer, pos.0, dir.0); + + RunAnimation::update_skeleton(&mut state.skeleton, time); + } + + self.states.retain(|entity, _| ecs.entities().is_alive(*entity)); } - pub fn update_locals(&mut self, renderer: &mut Renderer, locals: FigureLocals) -> Result<(), Error> { - renderer.update_consts(&mut self.locals, &[locals])?; - Ok(()) - } - - pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) { - renderer.render_figure( - &self.model, - globals, - &self.locals, - &self.bone_consts, - ); + pub fn render(&self, renderer: &mut Renderer, client: &Client, globals: &Consts<Globals>) { + for state in self.states.values() { + renderer.render_figure( + &self.test_model, + globals, + &state.locals, + &state.bone_consts, + ); + } } } -/* -#[derive(Copy, Clone, Debug)] -pub struct Figure<S: Skeleton> { +pub struct FigureState<S: Skeleton> { bone_consts: Consts<FigureBoneData>, locals: Consts<FigureLocals>, skeleton: S, } -impl<S: Skeleton> Component for Figure<S> { - type Storage = VecStorage<Self>; +impl<S: Skeleton> FigureState<S> { + pub fn new(renderer: &mut Renderer, skeleton: S) -> Self { + Self { + bone_consts: renderer.create_consts(&skeleton.compute_matrices()).unwrap(), + locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(), + skeleton, + } + } + + fn update(&mut self, renderer: &mut Renderer, pos: Vec3<f32>, dir: Vec3<f32>) { + let mat = + Mat4::<f32>::identity() * + Mat4::translation_3d(pos) * + Mat4::rotation_z(dir.y.atan2(dir.x) + f32::consts::PI / 2.0); + + let locals = FigureLocals::new(mat); + renderer.update_consts(&mut self.locals, &[locals]).unwrap(); + + renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()).unwrap(); + } } -*/ diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index e3e885b1ea..d2324d6758 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -29,7 +29,7 @@ use crate::{ }; use self::{ camera::Camera, - figure::Figure, + figure::Figures, terrain::Terrain, }; @@ -47,13 +47,7 @@ pub struct Scene { skybox: Skybox, terrain: Terrain, - - test_figure: Figure<CharacterSkeleton>, -} - -// TODO: Make a proper asset loading system -fn load_segment(filename: &'static str) -> Segment { - Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/voxygen/voxel/").to_string() + filename)).unwrap()) + figures: Figures, } impl Scene { @@ -74,30 +68,7 @@ impl Scene { .unwrap(), }, terrain: Terrain::new(), - - test_figure: Figure::new( - renderer, - [ - Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -6.5, -6.0))), - Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))), - Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), - Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))), - Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), - Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))), - Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), - Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))), - Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))), - None, - None, - None, - None, - None, - None, - None, - ], - CharacterSkeleton::new(), - ) - .unwrap(), + figures: Figures::new(renderer), } } @@ -133,7 +104,7 @@ impl Scene { } /// Maintain data such as GPU constant buffers, models, etc. To be called once per tick. - pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) { // Get player position let player_pos = client .player() @@ -165,24 +136,13 @@ impl Scene { )]) .expect("Failed to update global constants"); - // Maintain the terrain + // Maintain the terrain and figures self.terrain.maintain(renderer, client); - - // TODO: Don't do this here - RunAnimation::update_skeleton( - &mut self.test_figure.skeleton, - client.state().get_time(), - ); - - // Calculate player model matrix - let model_mat = Mat4::<f32>::translation_3d(player_pos); - - self.test_figure.update_locals(renderer, FigureLocals::new(model_mat)).unwrap(); - self.test_figure.update_skeleton(renderer).unwrap(); + self.figures.maintain(renderer, client); } /// Render the scene using the provided `Renderer` - pub fn render_to(&self, renderer: &mut Renderer) { + pub fn render(&self, renderer: &mut Renderer, client: &Client) { // Render the skybox first (it appears over everything else so must be rendered first) renderer.render_skybox( &self.skybox.model, @@ -190,10 +150,8 @@ impl Scene { &self.skybox.locals, ); - // Render terrain + // Render terrain and figures self.terrain.render(renderer, &self.globals); - - // Render the test figure - self.test_figure.render(renderer, &self.globals); + self.figures.render(renderer, client, &self.globals); } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index d11a6dfa13..fc9ec54126 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -87,7 +87,7 @@ impl SessionState { renderer.clear(BG_COLOR); // Render the screen using the global renderer - self.scene.render_to(renderer); + self.scene.render(renderer, &self.client.borrow()); // Draw the UI to the screen self.hud.render(renderer); @@ -157,7 +157,7 @@ impl PlayState for SessionState { .expect("Failed to tick the scene"); // Maintain the scene - self.scene.maintain(global_state.window.renderer_mut(), &self.client.borrow()); + self.scene.maintain(global_state.window.renderer_mut(), &mut self.client.borrow_mut()); // Maintain the UI for event in self.hud.maintain(global_state.window.renderer_mut()) { match event { From 7f6599e95523e972919d632ca0a88cae39c018ba Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Thu, 11 Apr 2019 00:16:29 +0100 Subject: [PATCH 03/16] Started work on server-side chunks Former-commit-id: 84a6bd7358f67a77043c4b11c787538f073c8d28 --- client/Cargo.toml | 1 - client/src/lib.rs | 17 ++++------ common/src/msg/client.rs | 2 +- common/src/msg/server.rs | 8 ++++- common/src/net/post.rs | 4 +-- common/src/state.rs | 41 ++++++++++++++++------- common/src/terrain/biome.rs | 3 ++ common/src/terrain/block.rs | 4 +-- common/src/terrain/mod.rs | 6 ++-- common/src/volumes/chunk.rs | 2 ++ common/src/volumes/vol_map.rs | 4 +++ server/Cargo.toml | 1 + server/src/client.rs | 32 ++++++++++-------- server/src/lib.rs | 63 +++++++++++++++++++++++++++-------- voxygen/src/menu/main/mod.rs | 4 +-- 15 files changed, 130 insertions(+), 62 deletions(-) diff --git a/client/Cargo.toml b/client/Cargo.toml index ac82be29e1..bc4284934f 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -6,7 +6,6 @@ edition = "2018" [dependencies] common = { package = "veloren-common", path = "../common" } -world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" diff --git a/client/src/lib.rs b/client/src/lib.rs index 0101408ed4..c20cca643c 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -15,7 +15,7 @@ use std::{ net::SocketAddr, }; use vek::*; -use threadpool; +use threadpool::ThreadPool; use specs::Builder; use common::{ comp, @@ -24,7 +24,6 @@ use common::{ net::PostBox, msg::{ClientMsg, ServerMsg}, }; -use world::World; const SERVER_TIMEOUT: f64 = 5.0; // Seconds @@ -33,7 +32,7 @@ pub enum Event { } pub struct Client { - thread_pool: threadpool::ThreadPool, + thread_pool: ThreadPool, last_ping: f64, postbox: PostBox<ClientMsg, ServerMsg>, @@ -41,10 +40,7 @@ pub struct Client { tick: u64, state: State, player: Option<EcsEntity>, - - // Testing - world: World, - pub chunk: Option<TerrainChunk>, + view_distance: u64, } impl Client { @@ -54,6 +50,7 @@ impl Client { addr: A, player: comp::Player, character: Option<comp::Character>, + view_distance: u64, ) -> Result<Self, Error> { let mut postbox = PostBox::to_server(addr)?; @@ -85,10 +82,7 @@ impl Client { tick: 0, state, player, - - // Testing - world: World::new(), - chunk: None, + view_distance, }) } @@ -204,6 +198,7 @@ impl Client { ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here! ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package), + ServerMsg::TerrainChunkUpdate { key, chunk } => self.state.insert_chunk(key, chunk), } } } else if let Some(err) = self.postbox.error() { diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index e8b59414ed..e2339aada3 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,6 +1,6 @@ use crate::comp; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub enum ClientMsg { Connect { player: comp::Player, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 21a21f506e..db0ae2ed81 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,6 +1,8 @@ +use vek::*; +use crate::terrain::TerrainChunk; use super::EcsPacket; -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub enum ServerMsg { Handshake { ecs_state: sphynx::StatePackage<EcsPacket>, @@ -12,4 +14,8 @@ pub enum ServerMsg { Chat(String), SetPlayerEntity(u64), EcsSync(sphynx::SyncPackage<EcsPacket>), + TerrainChunkUpdate { + key: Vec3<i32>, + chunk: TerrainChunk, + }, } diff --git a/common/src/net/post.rs b/common/src/net/post.rs index afd3d8956d..6338424912 100644 --- a/common/src/net/post.rs +++ b/common/src/net/post.rs @@ -56,8 +56,8 @@ impl<T> From<mio_extras::channel::SendError<T>> for Error { } } -pub trait PostSend = 'static + serde::Serialize + Send + fmt::Debug; -pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + fmt::Debug; +pub trait PostSend = 'static + serde::Serialize + Send; +pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send; const TCP_TOK: Token = Token(0); const CTRL_TOK: Token = Token(1); diff --git a/common/src/state.rs b/common/src/state.rs index 1e7f9a076d..6c7a0ba9b9 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -1,4 +1,7 @@ -use std::time::Duration; +use std::{ + time::Duration, + collections::HashSet, +}; use shred::{Fetch, FetchMut}; use specs::{ Builder, @@ -17,7 +20,10 @@ use vek::*; use crate::{ comp, sys, - terrain::TerrainMap, + terrain::{ + TerrainMap, + TerrainChunk, + }, msg::EcsPacket, }; @@ -36,17 +42,17 @@ struct Time(f64); pub struct DeltaTime(pub f64); pub struct Changes { - pub new_chunks: Vec<Vec3<i32>>, - pub changed_chunks: Vec<Vec3<i32>>, - pub removed_chunks: Vec<Vec3<i32>>, + pub new_chunks: HashSet<Vec3<i32>>, + pub changed_chunks: HashSet<Vec3<i32>>, + pub removed_chunks: HashSet<Vec3<i32>>, } impl Changes { pub fn default() -> Self { Self { - new_chunks: vec![], - changed_chunks: vec![], - removed_chunks: vec![], + new_chunks: HashSet::new(), + changed_chunks: HashSet::new(), + removed_chunks: HashSet::new(), } } @@ -152,12 +158,23 @@ impl State { /// Get a reference to this state's terrain. pub fn terrain(&self) -> Fetch<TerrainMap> { - self.ecs.internal().read_resource::<TerrainMap>() + self.ecs + .internal() + .read_resource::<TerrainMap>() } - // TODO: Get rid of this since it shouldn't be needed - pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> { - self.ecs.internal_mut().write_resource::<TerrainMap>() + /// Insert the provided chunk into this state's terrain. + pub fn insert_chunk(&mut self, key: Vec3<i32>, chunk: TerrainChunk) { + if self.ecs + .internal_mut() + .write_resource::<TerrainMap>() + .insert(key, chunk) + .is_some() + { + self.changes.changed_chunks.insert(key); + } else { + self.changes.new_chunks.insert(key); + } } /// Execute a single tick, simulating the game state by the given duration. diff --git a/common/src/terrain/biome.rs b/common/src/terrain/biome.rs index 81bd29967d..a177c44158 100644 --- a/common/src/terrain/biome.rs +++ b/common/src/terrain/biome.rs @@ -1,3 +1,6 @@ +use serde_derive::{Serialize, Deserialize}; + +#[derive(Clone, Serialize, Deserialize)] pub enum BiomeKind { Void, Grassland, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index d24314f2e9..6098a5c5dd 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,10 +1,10 @@ -// Library use vek::*; +use serde_derive::{Serialize, Deserialize}; // Crate use crate::vol::Vox; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Block { kind: u8, color: [u8; 3], diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index f860e14bcb..97008cde50 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -7,10 +7,8 @@ pub use self::{ biome::BiomeKind, }; -// Library use vek::*; - -// Crate +use serde_derive::{Serialize, Deserialize}; use crate::{ vol::VolSize, volumes::{ @@ -21,6 +19,7 @@ use crate::{ // TerrainChunkSize +#[derive(Clone, Serialize, Deserialize)] pub struct TerrainChunkSize; impl VolSize for TerrainChunkSize { @@ -29,6 +28,7 @@ impl VolSize for TerrainChunkSize { // TerrainChunkMeta +#[derive(Clone, Serialize, Deserialize)] pub struct TerrainChunkMeta { biome: BiomeKind, } diff --git a/common/src/volumes/chunk.rs b/common/src/volumes/chunk.rs index abdea07997..1c99301b63 100644 --- a/common/src/volumes/chunk.rs +++ b/common/src/volumes/chunk.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; // Library use vek::*; +use serde_derive::{Serialize, Deserialize}; // Local use crate::vol::{ @@ -23,6 +24,7 @@ pub enum ChunkErr { // V = Voxel // S = Size (replace when const generics are a thing) // M = Metadata +#[derive(Clone, Serialize, Deserialize)] pub struct Chunk<V: Vox, S: VolSize, M> { vox: Vec<V>, meta: M, diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index 954ac5cc16..2060b63989 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -129,4 +129,8 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> { pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> { self.chunks.remove(key) } + + pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> { + key * S::SIZE.map(|e| e as i32) + } } diff --git a/server/Cargo.toml b/server/Cargo.toml index f7f9f31dcb..a5b590a902 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" +threadpool = "1.7" diff --git a/server/src/client.rs b/server/src/client.rs index fed88d9ae4..71d8aae6ab 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use specs::Entity as EcsEntity; use common::{ comp, @@ -14,7 +15,6 @@ pub enum ClientState { pub struct Client { pub state: ClientState, - pub entity: EcsEntity, pub postbox: PostBox<ServerMsg, ClientMsg>, pub last_ping: f64, } @@ -26,36 +26,42 @@ impl Client { } pub struct Clients { - clients: Vec<Client>, + clients: HashMap<EcsEntity, Client>, } impl Clients { pub fn empty() -> Self { Self { - clients: Vec::new(), + clients: HashMap::new(), } } - pub fn add(&mut self, client: Client) { - self.clients.push(client); + pub fn add(&mut self, entity: EcsEntity, client: Client) { + self.clients.insert(entity, client); } - pub fn remove_if<F: FnMut(&mut Client) -> bool>(&mut self, f: F) { - self.clients.drain_filter(f); + pub fn remove_if<F: FnMut(EcsEntity, &mut Client) -> bool>(&mut self, mut f: F) { + self.clients.retain(|entity, client| !f(*entity, client)); + } + + pub fn notify(&mut self, entity: EcsEntity, msg: ServerMsg) { + if let Some(client) = self.clients.get_mut(&entity) { + client.notify(msg); + } } pub fn notify_connected(&mut self, msg: ServerMsg) { - for client in &mut self.clients { + for client in self.clients.values_mut() { if client.state == ClientState::Connected { - client.postbox.send(msg.clone()); + client.notify(msg.clone()); } } } - pub fn notify_connected_except(&mut self, entity: EcsEntity, msg: ServerMsg) { - for client in &mut self.clients { - if client.entity != entity && client.state == ClientState::Connected { - client.postbox.send(msg.clone()); + pub fn notify_connected_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) { + for (entity, client) in self.clients.iter_mut() { + if client.state == ClientState::Connected && *entity != except_entity { + client.notify(msg.clone()); } } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 35946c683e..0d3d4f872c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -13,6 +13,7 @@ pub use crate::{ use std::{ time::Duration, net::SocketAddr, + sync::mpsc, }; use specs::{ Entity as EcsEntity, @@ -22,11 +23,13 @@ use specs::{ saveload::MarkedBuilder, }; use vek::*; +use threadpool::ThreadPool; use common::{ comp, state::State, net::PostOffice, msg::{ServerMsg, ClientMsg}, + terrain::TerrainChunk, }; use world::World; use crate::client::{ @@ -56,18 +59,30 @@ pub struct Server { postoffice: PostOffice<ServerMsg, ClientMsg>, clients: Clients, + + thread_pool: ThreadPool, + chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>, + chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>, } impl Server { /// Create a new `Server`. #[allow(dead_code)] pub fn new() -> Result<Self, Error> { + let (chunk_tx, chunk_rx) = mpsc::channel(); + Ok(Self { state: State::new(), world: World::new(), postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?, clients: Clients::empty(), + + thread_pool: threadpool::Builder::new() + .thread_name("veloren-worker".into()) + .build(), + chunk_tx, + chunk_rx, }) } @@ -119,6 +134,27 @@ impl Server { // Tick the client's LocalState (step 3) self.state.tick(dt); + // Fetch any generated `TerrainChunk`s and insert them into the terrain + // Also, send the chunk data to anybody that is close by + for (key, chunk) in self.chunk_rx.try_iter() { + // Send the chunk to all nearby players + for (entity, player, pos) in ( + &self.state.ecs().internal().entities(), + &self.state.ecs().internal().read_storage::<comp::Player>(), + &self.state.ecs().internal().read_storage::<comp::phys::Pos>(), + ).join() { + // TODO: Distance check + // if self.state.terrain().key_pos(key) + + self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { + key, + chunk: chunk.clone(), + }); + } + + self.state.insert_chunk(key, chunk); + } + // Synchronise clients with the new state of the world self.sync_clients(); @@ -143,9 +179,8 @@ impl Server { .create_entity_synced() .build(); - self.clients.add(Client { + self.clients.add(entity, Client { state: ClientState::Connecting, - entity, postbox, last_ping: self.state.get_time(), }); @@ -166,7 +201,7 @@ impl Server { let mut new_chat_msgs = Vec::new(); let mut disconnected_clients = Vec::new(); - self.clients.remove_if(|client| { + self.clients.remove_if(|entity, client| { let mut disconnect = false; let new_msgs = client.postbox.new_messages(); @@ -181,12 +216,12 @@ impl Server { ClientMsg::Connect { player, character } => { // Write client components - state.write_component(client.entity, player); - state.write_component(client.entity, comp::phys::Pos(Vec3::zero())); - state.write_component(client.entity, comp::phys::Vel(Vec3::zero())); - state.write_component(client.entity, comp::phys::Dir(Vec3::unit_y())); + state.write_component(entity, player); + state.write_component(entity, comp::phys::Pos(Vec3::zero())); + state.write_component(entity, comp::phys::Vel(Vec3::zero())); + state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); if let Some(character) = character { - state.write_component(client.entity, character); + state.write_component(entity, character); } client.state = ClientState::Connected; @@ -196,7 +231,7 @@ impl Server { ecs_state: state.ecs().gen_state_package(), player_entity: state .ecs() - .uid_from_entity(client.entity) + .uid_from_entity(entity) .unwrap() .into(), }); @@ -207,11 +242,11 @@ impl Server { ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), ClientMsg::Pong => {}, - ClientMsg::Chat(msg) => new_chat_msgs.push((client.entity, msg)), + ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), ClientMsg::PlayerPhysics { pos, vel, dir } => { - state.write_component(client.entity, pos); - state.write_component(client.entity, vel); - state.write_component(client.entity, dir); + state.write_component(entity, pos); + state.write_component(entity, vel); + state.write_component(entity, dir); }, ClientMsg::Disconnect => disconnect = true, }, @@ -228,7 +263,7 @@ impl Server { } if disconnect { - disconnected_clients.push(client.entity); + disconnected_clients.push(entity); true } else { false diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 4b95bb6d46..88c08cb0b8 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -58,7 +58,7 @@ impl PlayState for MainMenuState { global_state.window.renderer_mut().clear(BG_COLOR); - // Maintain the UI + // Maintain the UI (TODO: Maybe clean this up a little to avoid rightward drift?) for event in self.main_menu_ui.maintain(global_state.window.renderer_mut()) { match event { MainMenuEvent::LoginAttempt{ username, server_address } => { @@ -70,7 +70,7 @@ impl PlayState for MainMenuState { Ok(mut socket_adders) => { while let Some(socket_addr) = socket_adders.next() { // TODO: handle error - match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test())) { + match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test()), 300) { Ok(client) => { return PlayStateResult::Push( Box::new(CharSelectionState::new( From d4af18e3d4b8e4d9898c2d3c882a410b0cf667c9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Thu, 11 Apr 2019 00:41:37 +0100 Subject: [PATCH 04/16] Added server-side chunk generation Former-commit-id: 72e02f8ec711ec4fac3111b591c4b08086dee4e3 --- common/src/msg/client.rs | 4 ++++ common/src/volumes/vol_map.rs | 8 ++++++-- server/src/lib.rs | 25 ++++++++++++++++++++++++- world/src/lib.rs | 2 +- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index e2339aada3..5a599633d7 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,3 +1,4 @@ +use vek::*; use crate::comp; #[derive(Clone, Serialize, Deserialize)] @@ -14,5 +15,8 @@ pub enum ClientMsg { vel: comp::phys::Vel, dir: comp::phys::Dir, }, + TerrainChunkRequest { + key: Vec3<i32>, + }, Disconnect, } diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index 2060b63989..ab2947a416 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -126,8 +126,12 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> { self.chunks.insert(key, chunk) } - pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> { - self.chunks.remove(key) + pub fn get_key(&self, key: Vec3<i32>) -> Option<&Chunk<V, S, M>> { + self.chunks.get(&key) + } + + pub fn remove(&mut self, key: Vec3<i32>) -> Option<Chunk<V, S, M>> { + self.chunks.remove(&key) } pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> { diff --git a/server/src/lib.rs b/server/src/lib.rs index 0d3d4f872c..9313505a71 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -14,6 +14,7 @@ use std::{ time::Duration, net::SocketAddr, sync::mpsc, + collections::HashSet, }; use specs::{ Entity as EcsEntity, @@ -63,6 +64,7 @@ pub struct Server { thread_pool: ThreadPool, chunk_tx: mpsc::Sender<(Vec3<i32>, TerrainChunk)>, chunk_rx: mpsc::Receiver<(Vec3<i32>, TerrainChunk)>, + pending_chunks: HashSet<Vec3<i32>>, } impl Server { @@ -83,6 +85,7 @@ impl Server { .build(), chunk_tx, chunk_rx, + pending_chunks: HashSet::new(), }) } @@ -200,6 +203,7 @@ impl Server { let state = &mut self.state; let mut new_chat_msgs = Vec::new(); let mut disconnected_clients = Vec::new(); + let mut requested_chunks = Vec::new(); self.clients.remove_if(|entity, client| { let mut disconnect = false; @@ -240,6 +244,7 @@ impl Server { }, ClientState::Connected => match msg { ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected + ClientMsg::Disconnect => disconnect = true, ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), ClientMsg::Pong => {}, ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), @@ -248,7 +253,13 @@ impl Server { state.write_component(entity, vel); state.write_component(entity, dir); }, - ClientMsg::Disconnect => disconnect = true, + ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) { + Some(chunk) => client.postbox.send(ServerMsg::TerrainChunkUpdate { + key, + chunk: chunk.clone(), + }), + None => requested_chunks.push(key), + }, }, } } @@ -297,6 +308,11 @@ impl Server { }); } + // Generate requested chunks + for key in requested_chunks { + self.generate_chunk(key); + } + Ok(frontend_events) } @@ -304,6 +320,13 @@ impl Server { fn sync_clients(&mut self) { self.clients.notify_connected(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package())); } + + pub fn generate_chunk(&mut self, key: Vec3<i32>) { + if self.pending_chunks.insert(key) { + let chunk_tx = self.chunk_tx.clone(); + self.thread_pool.execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap()); + } + } } impl Drop for Server { diff --git a/world/src/lib.rs b/world/src/lib.rs index b296ff2e6b..5c9d57b42c 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -24,7 +24,7 @@ impl World { Self } - pub fn generate_chunk(&self, chunk_pos: Vec3<i32>) -> TerrainChunk { + pub fn generate_chunk(chunk_pos: Vec3<i32>) -> TerrainChunk { // TODO: This is all test code, remove/improve this later let mut chunk = TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void()); From f4ce38e58a10bb3d6a16f0b7292f7d1e666698a6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Thu, 11 Apr 2019 23:26:43 +0100 Subject: [PATCH 05/16] Rewrote netcode, added basic chunk synching Former-commit-id: e9f76f7fa9dbe0c81cd4c998bf0f0b3eec9235cb --- chat-cli/src/main.rs | 2 +- client/src/error.rs | 5 +- client/src/lib.rs | 43 +++- common/Cargo.toml | 1 - common/src/lib.rs | 2 +- common/src/msg/client.rs | 2 + common/src/msg/server.rs | 4 +- common/src/net/mod.rs | 5 +- common/src/net/post.rs | 244 +++++++++++++-------- common/src/net/post2.rs | 402 ++++++++++++++++++++++++++++++++++ common/src/volumes/vol_map.rs | 4 + server/src/client.rs | 2 +- server/src/lib.rs | 12 +- 13 files changed, 608 insertions(+), 120 deletions(-) create mode 100644 common/src/net/post2.rs diff --git a/chat-cli/src/main.rs b/chat-cli/src/main.rs index deb5cb126c..2bbc51a388 100644 --- a/chat-cli/src/main.rs +++ b/chat-cli/src/main.rs @@ -18,7 +18,7 @@ fn main() { let mut clock = Clock::new(); // Create client - let mut client = Client::new(([127, 0, 0, 1], 59003), comp::Player::new("test".to_string()), None) + let mut client = Client::new(([127, 0, 0, 1], 59003), comp::Player::new("test".to_string()), None, 300) .expect("Failed to create client instance"); client.send_chat("Hello!".to_string()); diff --git a/client/src/error.rs b/client/src/error.rs index b4b3ce4742..1f74ae62b9 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -11,9 +11,6 @@ pub enum Error { impl From<PostError> for Error { fn from(err: PostError) -> Self { - match err { - PostError::Disconnect => Error::ServerShutdown, - err => Error::Network(err), - } + Error::Network(err) } } diff --git a/client/src/lib.rs b/client/src/lib.rs index c20cca643c..1d5bdc1a00 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -13,6 +13,7 @@ pub use crate::{ use std::{ time::Duration, net::SocketAddr, + collections::HashSet, }; use vek::*; use threadpool::ThreadPool; @@ -41,6 +42,8 @@ pub struct Client { state: State, player: Option<EcsEntity>, view_distance: u64, + + pending_chunks: HashSet<Vec3<i32>>, } impl Client { @@ -53,10 +56,10 @@ impl Client { view_distance: u64, ) -> Result<Self, Error> { - let mut postbox = PostBox::to_server(addr)?; + let mut postbox = PostBox::to(addr)?; // Send connection request - postbox.send(ClientMsg::Connect { + postbox.send_message(ClientMsg::Connect { player, character, }); @@ -83,6 +86,8 @@ impl Client { state, player, view_distance, + + pending_chunks: HashSet::new(), }) } @@ -115,7 +120,7 @@ impl Client { /// Send a chat message to the server #[allow(dead_code)] pub fn send_chat(&mut self, msg: String) { - self.postbox.send(ClientMsg::Chat(msg)) + self.postbox.send_message(ClientMsg::Chat(msg)) } /// Execute a single client tick, handle input and update the game state by the given duration @@ -161,12 +166,31 @@ impl Client { self.state.read_storage().get(ecs_entity).cloned(), ) { (Some(pos), Some(vel), Some(dir)) => { - self.postbox.send(ClientMsg::PlayerPhysics { pos, vel, dir }); + self.postbox.send_message(ClientMsg::PlayerPhysics { pos, vel, dir }); }, _ => {}, } } + // Request chunks from the server + if let Some(player_entity) = self.player { + if let Some(pos) = self.state.read_storage::<comp::phys::Pos>().get(player_entity) { + let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); + + for i in chunk_pos.x - 0..chunk_pos.x + 1 { + for j in chunk_pos.y - 0..chunk_pos.y + 1 { + for k in 0..3 { + let key = chunk_pos + Vec3::new(i, j, k); + if self.state.terrain().get_key(key).is_none() && !self.pending_chunks.contains(&key) { + self.postbox.send_message(ClientMsg::TerrainChunkRequest { key }); + self.pending_chunks.insert(key); + } + } + } + } + } + } + // Finish the tick, pass control back to the frontend (step 6) self.tick += 1; Ok(frontend_events) @@ -193,12 +217,15 @@ impl Client { match msg { ServerMsg::Handshake { .. } => return Err(Error::ServerWentMad), ServerMsg::Shutdown => return Err(Error::ServerShutdown), - ServerMsg::Ping => self.postbox.send(ClientMsg::Pong), + ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong), ServerMsg::Pong => {}, ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here! ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package), - ServerMsg::TerrainChunkUpdate { key, chunk } => self.state.insert_chunk(key, chunk), + ServerMsg::TerrainChunkUpdate { key, chunk } => { + self.state.insert_chunk(key, *chunk); + self.pending_chunks.remove(&key); + }, } } } else if let Some(err) = self.postbox.error() { @@ -207,7 +234,7 @@ impl Client { return Err(Error::ServerTimeout); } else if self.state.get_time() - self.last_ping > SERVER_TIMEOUT * 0.5 { // Try pinging the server if the timeout is nearing - self.postbox.send(ClientMsg::Ping); + self.postbox.send_message(ClientMsg::Ping); } Ok(frontend_events) @@ -216,6 +243,6 @@ impl Client { impl Drop for Client { fn drop(&mut self) { - self.postbox.send(ClientMsg::Disconnect); + self.postbox.send_message(ClientMsg::Disconnect); } } diff --git a/common/Cargo.toml b/common/Cargo.toml index 75c032bf18..8c82e841a3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -18,4 +18,3 @@ serde = "1.0" serde_derive = "1.0" bincode = "1.0" log = "0.4" -pretty_env_logger = "0.3" diff --git a/common/src/lib.rs b/common/src/lib.rs index 72bcade3f8..146e272351 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(euclidean_division, duration_float, trait_alias)] +#![feature(euclidean_division, duration_float, trait_alias, bind_by_move_pattern_guards)] #[macro_use] extern crate serde_derive; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 5a599633d7..ffc941b1b6 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -20,3 +20,5 @@ pub enum ClientMsg { }, Disconnect, } + +impl middleman::Message for ClientMsg {} diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index db0ae2ed81..bdd86945ca 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -16,6 +16,8 @@ pub enum ServerMsg { EcsSync(sphynx::SyncPackage<EcsPacket>), TerrainChunkUpdate { key: Vec3<i32>, - chunk: TerrainChunk, + chunk: Box<TerrainChunk>, }, } + +impl middleman::Message for ServerMsg {} diff --git a/common/src/net/mod.rs b/common/src/net/mod.rs index c1c7fd4107..11829e32fa 100644 --- a/common/src/net/mod.rs +++ b/common/src/net/mod.rs @@ -1,5 +1,8 @@ pub mod data; -pub mod post; +//pub mod post; +pub mod post2; + +pub use post2 as post; // Reexports pub use self::{ diff --git a/common/src/net/post.rs b/common/src/net/post.rs index 6338424912..38a8533186 100644 --- a/common/src/net/post.rs +++ b/common/src/net/post.rs @@ -23,6 +23,7 @@ use mio_extras::channel::{ Sender, }; use bincode; +use middleman::Middleman; #[derive(Clone, Debug, PartialEq)] pub enum Error { @@ -56,14 +57,15 @@ impl<T> From<mio_extras::channel::SendError<T>> for Error { } } -pub trait PostSend = 'static + serde::Serialize + Send; -pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send; +pub trait PostSend = 'static + serde::Serialize + Send + middleman::Message; +pub trait PostRecv = 'static + serde::de::DeserializeOwned + Send + middleman::Message; -const TCP_TOK: Token = Token(0); -const CTRL_TOK: Token = Token(1); -const POSTBOX_TOK: Token = Token(2); -const SEND_TOK: Token = Token(3); -const RECV_TOK: Token = Token(4); +const TCP_TOK: Token = Token(0); +const CTRL_TOK: Token = Token(1); +const POSTBOX_TOK: Token = Token(2); +const SEND_TOK: Token = Token(3); +const RECV_TOK: Token = Token(4); +const MIDDLEMAN_TOK: Token = Token(5); const MAX_MSG_BYTES: usize = 1 << 20; @@ -218,7 +220,7 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> { let (recv_tx, recv_rx) = channel(); let worker_poll = Poll::new()?; - worker_poll.register(&tcp_stream, TCP_TOK, Ready::readable(), PollOpt::edge())?; + worker_poll.register(&tcp_stream, TCP_TOK, Ready::readable() | Ready::writable(), PollOpt::edge())?; worker_poll.register(&ctrl_rx, CTRL_TOK, Ready::readable(), PollOpt::edge())?; worker_poll.register(&send_rx, SEND_TOK, Ready::readable(), PollOpt::edge())?; @@ -345,12 +347,38 @@ fn postbox_worker<S: PostSend, R: PostRecv>( send_rx: Receiver<S>, recv_tx: Sender<Result<R, Error>>, ) -> Result<(), Error> { + fn try_tcp_send(tcp_stream: &mut TcpStream, chunks: &mut VecDeque<Vec<u8>>) -> Result<(), Error> { + loop { + let chunk = match chunks.pop_front() { + Some(chunk) => chunk, + None => break, + }; + + match tcp_stream.write_all(&chunk) { + Ok(()) => {}, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + chunks.push_front(chunk); + break; + }, + Err(err) => { + println!("Error: {:?}", err); + return Err(err.into()); + }, + } + } + + Ok(()) + } + enum RecvState { ReadHead(Vec<u8>), ReadBody(usize, Vec<u8>), } - let mut recv_state = RecvState::ReadHead(Vec::with_capacity(8)); + let mut recv_state = RecvState::ReadHead(Vec::new()); + let mut chunks = VecDeque::new(); + + //let mut recv_state = RecvState::ReadHead(Vec::with_capacity(8)); let mut events = Events::with_capacity(64); 'work: loop { @@ -383,18 +411,23 @@ fn postbox_worker<S: PostSend, R: PostRecv>( }, }; - let mut packet = msg_bytes + let mut bytes = msg_bytes .len() .to_le_bytes() .as_ref() .to_vec(); - packet.append(&mut msg_bytes); + bytes.append(&mut msg_bytes); - match tcp_stream.write_all(&packet) { - Ok(()) => {}, + bytes + .chunks(1024) + .map(|chunk| chunk.to_vec()) + .for_each(|chunk| chunks.push_back(chunk)); + + match try_tcp_send(&mut tcp_stream, &mut chunks) { + Ok(_) => {}, Err(err) => { recv_tx.send(Err(err.into()))?; - break 'work; + return Err(Error::Network); }, } }, @@ -402,61 +435,75 @@ fn postbox_worker<S: PostSend, R: PostRecv>( Err(err) => Err(err)?, } }, - TCP_TOK => loop { - match tcp_stream.take_error() { - Ok(None) => {}, - Ok(Some(err)) => { - recv_tx.send(Err(err.into()))?; - break 'work; - }, + TCP_TOK => { + loop { + // Check TCP error + match tcp_stream.take_error() { + Ok(None) => {}, + Ok(Some(err)) => { + recv_tx.send(Err(err.into()))?; + break 'work; + }, + Err(err) => { + recv_tx.send(Err(err.into()))?; + break 'work; + }, + } + match &mut recv_state { + RecvState::ReadHead(head) => if head.len() == 8 { + let len = usize::from_le_bytes(<[u8; 8]>::try_from(head.as_slice()).unwrap()); + if len > MAX_MSG_BYTES { + println!("TOO BIG! {:x}", len); + recv_tx.send(Err(Error::InvalidMsg))?; + break 'work; + } else if len == 0 { + recv_state = RecvState::ReadHead(Vec::with_capacity(8)); + break; + } else { + recv_state = RecvState::ReadBody( + len, + Vec::new(), + ); + } + } else { + let mut b = [0; 1]; + match tcp_stream.read(&mut b) { + Ok(0) => {}, + Ok(_) => head.push(b[0]), + Err(_) => break, + } + }, + RecvState::ReadBody(len, body) => if body.len() == *len { + match bincode::deserialize(&body) { + Ok(msg) => { + recv_tx.send(Ok(msg))?; + recv_state = RecvState::ReadHead(Vec::with_capacity(8)); + }, + Err(err) => { + recv_tx.send(Err((*err).into()))?; + break 'work; + }, + } + } else { + let left = *len - body.len(); + let mut buf = vec![0; left]; + match tcp_stream.read(&mut buf) { + Ok(_) => body.append(&mut buf), + Err(err) => { + recv_tx.send(Err(err.into()))?; + break 'work; + }, + } + }, + } + } + + // Now, try sending TCP stuff + match try_tcp_send(&mut tcp_stream, &mut chunks) { + Ok(_) => {}, Err(err) => { recv_tx.send(Err(err.into()))?; - break 'work; - }, - } - match &mut recv_state { - RecvState::ReadHead(head) => if head.len() == 8 { - let len = usize::from_le_bytes(<[u8; 8]>::try_from(head.as_slice()).unwrap()); - if len > MAX_MSG_BYTES { - recv_tx.send(Err(Error::InvalidMsg))?; - break 'work; - } else if len == 0 { - recv_state = RecvState::ReadHead(Vec::with_capacity(8)); - break; - } else { - recv_state = RecvState::ReadBody( - len, - Vec::new(), - ); - } - } else { - let mut b = [0; 1]; - match tcp_stream.read(&mut b) { - Ok(_) => head.push(b[0]), - Err(_) => break, - } - }, - RecvState::ReadBody(len, body) => if body.len() == *len { - match bincode::deserialize(&body) { - Ok(msg) => { - recv_tx.send(Ok(msg))?; - recv_state = RecvState::ReadHead(Vec::with_capacity(8)); - }, - Err(err) => { - recv_tx.send(Err((*err).into()))?; - break 'work; - }, - } - } else { - let left = *len - body.len(); - let mut buf = vec![0; left]; - match tcp_stream.read(&mut buf) { - Ok(_) => body.append(&mut buf), - Err(err) => { - recv_tx.send(Err(err.into()))?; - break 'work; - }, - } + return Err(Error::Network); }, } }, @@ -465,24 +512,28 @@ fn postbox_worker<S: PostSend, R: PostRecv>( } } - tcp_stream.shutdown(Shutdown::Both)?; + //tcp_stream.shutdown(Shutdown::Both)?; Ok(()) } // TESTS +/* +#[derive(Serialize, Deserialize)] +struct TestMsg<T>(T); + #[test] fn connect() { let srv_addr = ([127, 0, 0, 1], 12345); - let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap(); + let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap(); // We should start off with 0 incoming connections thread::sleep(Duration::from_millis(250)); assert_eq!(postoffice.new_connections().len(), 0); assert_eq!(postoffice.error(), None); - let postbox = PostBox::<f32, u32>::to_server(srv_addr).unwrap(); + let postbox = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap(); // Now a postbox has been created, we should have 1 new thread::sleep(Duration::from_millis(250)); @@ -496,21 +547,21 @@ fn connect_fail() { let listen_addr = ([0; 4], 12345); let connect_addr = ([127, 0, 0, 1], 12212); - let mut postoffice = PostOffice::<u32, f32>::bind(listen_addr).unwrap(); + let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(listen_addr).unwrap(); // We should start off with 0 incoming connections thread::sleep(Duration::from_millis(250)); assert_eq!(postoffice.new_connections().len(), 0); assert_eq!(postoffice.error(), None); - assert!(PostBox::<f32, u32>::to_server(connect_addr).is_err()); + assert!(PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(connect_addr).is_err()); } #[test] fn connection_count() { let srv_addr = ([127, 0, 0, 1], 12346); - let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap(); + let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap(); let mut postboxes = Vec::new(); // We should start off with 0 incoming connections @@ -519,7 +570,7 @@ fn connection_count() { assert_eq!(postoffice.error(), None); for _ in 0..5 { - postboxes.push(PostBox::<f32, u32>::to_server(srv_addr).unwrap()); + postboxes.push(PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap()); } // 5 postboxes created, we should have 5 @@ -533,10 +584,10 @@ fn connection_count() { fn disconnect() { let srv_addr = ([127, 0, 0, 1], 12347); - let mut postoffice = PostOffice::<u32, f32>::bind(srv_addr).unwrap(); + let mut postoffice = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap(); let mut server_postbox = { - let mut client_postbox = PostBox::<f32, u32>::to_server(srv_addr).unwrap(); + let mut client_postbox = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap(); thread::sleep(Duration::from_millis(250)); let mut incoming = postoffice.new_connections(); @@ -558,52 +609,53 @@ fn disconnect() { fn client_to_server() { let srv_addr = ([127, 0, 0, 1], 12348); - let mut po = PostOffice::<u32, f32>::bind(srv_addr).unwrap(); + let mut po = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap(); - let mut client_pb = PostBox::<f32, u32>::to_server(srv_addr).unwrap(); + let mut client_pb = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap(); thread::sleep(Duration::from_millis(250)); let mut server_pb = po.new_connections().next().unwrap(); - client_pb.send(1337.0); - client_pb.send(9821.0); - client_pb.send(-3.2); - client_pb.send(17.0); + client_pb.send(TestMsg(1337.0)); + client_pb.send(TestMsg(9821.0)); + client_pb.send(TestMsg(-3.2)); + client_pb.send(TestMsg(17.0)); thread::sleep(Duration::from_millis(250)); let mut incoming_msgs = server_pb.new_messages(); assert_eq!(incoming_msgs.len(), 4); - assert_eq!(incoming_msgs.next().unwrap(), 1337.0); - assert_eq!(incoming_msgs.next().unwrap(), 9821.0); - assert_eq!(incoming_msgs.next().unwrap(), -3.2); - assert_eq!(incoming_msgs.next().unwrap(), 17.0); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(1337.0)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(9821.0)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(-3.2)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(17.0)); } #[test] fn server_to_client() { let srv_addr = ([127, 0, 0, 1], 12349); - let mut po = PostOffice::<u32, f32>::bind(srv_addr).unwrap(); + let mut po = PostOffice::<TestMsg<u32>, TestMsg<f32>>::bind(srv_addr).unwrap(); - let mut client_pb = PostBox::<f32, u32>::to_server(srv_addr).unwrap(); + let mut client_pb = PostBox::<TestMsg<f32>, TestMsg<u32>>::to_server(srv_addr).unwrap(); thread::sleep(Duration::from_millis(250)); let mut server_pb = po.new_connections().next().unwrap(); - server_pb.send(1337); - server_pb.send(9821); - server_pb.send(39999999); - server_pb.send(17); + server_pb.send(TestMsg(1337)); + server_pb.send(TestMsg(9821)); + server_pb.send(TestMsg(39999999)); + server_pb.send(TestMsg(17)); thread::sleep(Duration::from_millis(250)); let mut incoming_msgs = client_pb.new_messages(); assert_eq!(incoming_msgs.len(), 4); - assert_eq!(incoming_msgs.next().unwrap(), 1337); - assert_eq!(incoming_msgs.next().unwrap(), 9821); - assert_eq!(incoming_msgs.next().unwrap(), 39999999); - assert_eq!(incoming_msgs.next().unwrap(), 17); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(1337)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(9821)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(39999999)); + assert_eq!(incoming_msgs.next().unwrap(), TestMsg(17)); } +*/ diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs new file mode 100644 index 0000000000..16a9bb985a --- /dev/null +++ b/common/src/net/post2.rs @@ -0,0 +1,402 @@ +use std::{ + io::{self, Read, Write}, + net::{TcpListener, TcpStream, SocketAddr, Shutdown}, + time::{Instant, Duration}, + marker::PhantomData, + sync::mpsc, + thread, + collections::VecDeque, + convert::TryFrom, +}; +use serde::{Serialize, de::DeserializeOwned}; + +#[derive(Clone, Debug)] +pub enum Error { + Io, //Io(io::Error), + Bincode, //Bincode(bincode::Error), + ChannelFailure, + InvalidMessage, +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io //(err) + } +} + +impl From<bincode::Error> for Error { + fn from(err: bincode::Error) -> Self { + Error::Bincode //(err) + } +} + +impl From<mpsc::TryRecvError> for Error { + fn from(error: mpsc::TryRecvError) -> Self { + Error::ChannelFailure + } +} + +pub trait PostMsg = Serialize + DeserializeOwned + 'static + Send; + +const MAX_MSG_SIZE: usize = 1 << 20; + +pub struct PostOffice<S: PostMsg, R: PostMsg> { + listener: TcpListener, + error: Option<Error>, + phantom: PhantomData<(S, R)>, +} + +impl<S: PostMsg, R: PostMsg> PostOffice<S, R> { + pub fn bind<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> { + let mut listener = TcpListener::bind(addr.into())?; + listener.set_nonblocking(true)?; + + Ok(Self { + listener, + error: None, + phantom: PhantomData, + }) + } + + pub fn error(&self) -> Option<Error> { + self.error.clone() + } + + pub fn new_postboxes(&mut self) -> impl ExactSizeIterator<Item=PostBox<S, R>> { + let mut new = Vec::new(); + + if self.error.is_some() { + return new.into_iter(); + } + + loop { + match self.listener.accept() { + Ok((stream, sock)) => new.push(PostBox::from_stream(stream).unwrap()), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) => { + self.error = Some(e.into()); + break; + }, + } + } + + new.into_iter() + } +} + +pub struct PostBox<S: PostMsg, R: PostMsg> { + send_tx: mpsc::Sender<Option<S>>, + recv_rx: mpsc::Receiver<Result<R, Error>>, + worker: Option<thread::JoinHandle<()>>, + error: Option<Error>, +} + +impl<S: PostMsg, R: PostMsg> PostBox<S, R> { + pub fn to<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> { + Self::from_stream(TcpStream::connect(addr.into())?) + } + + fn from_stream(stream: TcpStream) -> Result<Self, Error> { + stream.set_nonblocking(true)?; + + let (send_tx, send_rx) = mpsc::channel(); + let (recv_tx, recv_rx) = mpsc::channel(); + + let worker = thread::spawn(move || Self::worker(stream, send_rx, recv_tx)); + + Ok(Self { + send_tx, + recv_rx, + worker: Some(worker), + error: None, + }) + } + + pub fn error(&self) -> Option<Error> { + self.error.clone() + } + + pub fn send_message(&mut self, msg: S) { + let _ = self.send_tx.send(Some(msg)); + } + + pub fn next_message(&mut self) -> Option<R> { + if self.error.is_some() { + return None; + } + + match self.recv_rx.recv().ok()? { + Ok(msg) => Some(msg), + Err(e) => { + self.error = Some(e); + None + }, + } + } + + pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> { + let mut new = Vec::new(); + + if self.error.is_some() { + return new.into_iter(); + } + + loop { + match self.recv_rx.try_recv() { + Ok(Ok(msg)) => new.push(msg), + Err(mpsc::TryRecvError::Empty) => break, + Err(e) => { + self.error = Some(e.into()); + break; + }, + Ok(Err(e)) => { + self.error = Some(e); + break; + }, + } + } + + new.into_iter() + } + + fn worker(mut stream: TcpStream, send_rx: mpsc::Receiver<Option<S>>, recv_tx: mpsc::Sender<Result<R, Error>>) { + let mut outgoing_chunks = VecDeque::new(); + let mut incoming_buf = Vec::new(); + + 'work: loop { + // Get stream errors + match stream.take_error() { + Ok(Some(e)) | Err(e) => { + recv_tx.send(Err(e.into())).unwrap(); + break 'work; + }, + Ok(None) => {}, + } + + // Try getting messages from the send channel + loop { + match send_rx.try_recv() { + Ok(Some(send_msg)) => { + // Serialize message + let mut msg_bytes = bincode::serialize(&send_msg).unwrap(); + + // Assemble into packet + let mut packet_bytes = msg_bytes + .len() + .to_le_bytes() + .as_ref() + .to_vec(); + packet_bytes.append(&mut msg_bytes); + + // Split packet into chunks + packet_bytes + .chunks(4096) + .map(|chunk| chunk.to_vec()) + .for_each(|chunk| outgoing_chunks.push_back(chunk)) + }, + // Shut down worker + Ok(None) => break 'work, + Err(mpsc::TryRecvError::Empty) => break, + // Worker error + Err(e) => { + let _ = recv_tx.send(Err(e.into())); + break 'work; + }, + } + } + + // Try sending bytes through the TCP stream + loop { + //println!("HERE! Outgoing len: {}", outgoing_chunks.len()); + match outgoing_chunks.pop_front() { + Some(chunk) => match stream.write_all(&chunk) { + Ok(()) => {}, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // Return chunk to the queue to try again later + outgoing_chunks.push_front(chunk); + break; + }, + // Worker error + Err(e) => { + recv_tx.send(Err(e.into())).unwrap(); + break 'work; + }, + }, + None => break, + } + } + + // Try receiving bytes from the TCP stream + loop { + let mut buf = [0; 1024]; + + match stream.read(&mut buf) { + Ok(n) => incoming_buf.extend_from_slice(&buf[0..n]), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + // Worker error + Err(e) => { + recv_tx.send(Err(e.into())).unwrap(); + break 'work; + }, + } + } + + // Try turning bytes into messages + loop { + match incoming_buf.get(0..8) { + Some(len_bytes) => { + let len = usize::from_le_bytes(<[u8; 8]>::try_from(len_bytes).unwrap()); // Can't fail + + if len > MAX_MSG_SIZE { + recv_tx.send(Err(Error::InvalidMessage)).unwrap(); + break 'work; + } else if incoming_buf.len() >= len + 8 { + let deserialize_result = bincode::deserialize(&incoming_buf[8..len + 8]); + + if let Err(e) = &deserialize_result { + println!("DESERIALIZE ERROR: {:?}", e); + } + + recv_tx.send(deserialize_result.map_err(|e| e.into())); + incoming_buf = incoming_buf.split_off(len + 8); + } else { + break; + } + }, + None => break, + } + } + + thread::sleep(Duration::from_millis(10)); + } + + stream.shutdown(Shutdown::Both); + } +} + +impl<S: PostMsg, R: PostMsg> Drop for PostBox<S, R> { + fn drop(&mut self) { + let _ = self.send_tx.send(None); + // TODO: Cleanly join! + //self.worker.take().map(|handle| handle.join()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn create_postoffice<S: PostMsg, R: PostMsg>(id: u16) -> Result<(PostOffice<S, R>, SocketAddr), Error> { + let sock = ([0; 4], 12345 + id).into(); + Ok((PostOffice::bind(sock)?, sock)) + } + + fn loop_for<F: FnMut()>(duration: Duration, mut f: F) { + let start = Instant::now(); + while start.elapsed() < duration { + f(); + } + } + + #[test] + fn connect() { + let (mut postoffice, sock) = create_postoffice::<(), ()>(0).unwrap(); + + let _client0 = PostBox::<(), ()>::to(sock).unwrap(); + let _client1 = PostBox::<(), ()>::to(sock).unwrap(); + let _client2 = PostBox::<(), ()>::to(sock).unwrap(); + + let mut new_clients = 0; + loop_for(Duration::from_millis(250), || { + new_clients += postoffice.new_postboxes().count(); + }); + + assert_eq!(new_clients, 3); + } + + /* + #[test] + fn disconnect() { + let (mut postoffice, sock) = create_postoffice::<(), ()>(1).unwrap(); + + let mut client = PostBox::<i32, ()>::to(sock).unwrap(); + loop_for(Duration::from_millis(250), || ()); + let mut server = postoffice.new_postboxes().unwrap().next().unwrap(); + + drop(client); + loop_for(Duration::from_millis(300), || ()); + + assert!(server.new_messages().is_err()); + } + */ + + #[test] + fn send_recv() { + let (mut postoffice, sock) = create_postoffice::<(), i32>(2).unwrap(); + let test_msgs = vec![1, 1337, 42, -48]; + + let mut client = PostBox::<i32, ()>::to(sock).unwrap(); + loop_for(Duration::from_millis(250), || ()); + let mut server = postoffice.new_postboxes().next().unwrap(); + + for msg in &test_msgs { + client.send_message(msg.clone()); + } + + let mut recv_msgs = Vec::new(); + loop_for(Duration::from_millis(250), || server + .new_messages() + .for_each(|msg| recv_msgs.push(msg))); + + assert_eq!(test_msgs, recv_msgs); + } + + #[test] + fn send_recv_huge() { + let (mut postoffice, sock) = create_postoffice::<(), Vec<i32>>(3).unwrap(); + let test_msgs: Vec<Vec<i32>> = (0..5).map(|i| (0..100000).map(|j| i * 2 + j).collect()).collect(); + + let mut client = PostBox::<Vec<i32>, ()>::to(sock).unwrap(); + loop_for(Duration::from_millis(250), || ()); + let mut server = postoffice.new_postboxes().next().unwrap(); + + for msg in &test_msgs { + client.send_message(msg.clone()); + } + + let mut recv_msgs = Vec::new(); + loop_for(Duration::from_millis(2000), || server + .new_messages() + .for_each(|msg| recv_msgs.push(msg))); + + assert_eq!(test_msgs.len(), recv_msgs.len()); + assert!(test_msgs == recv_msgs); + } + + #[test] + fn send_recv_both() { + let (mut postoffice, sock) = create_postoffice::<u32, u32>(4).unwrap(); + let test_msgs = vec![1, 1337, 42, -48]; + + let mut client = PostBox::<u32, u32>::to(sock).unwrap(); + loop_for(Duration::from_millis(250), || ()); + let mut server = postoffice.new_postboxes().next().unwrap(); + + let test_msgs = vec![ + (0xDEADBEAD, 0xBEEEEEEF), + (0x1BADB002, 0xBAADF00D), + (0xBAADA555, 0xC0DED00D), + (0xCAFEBABE, 0xDEADC0DE), + ]; + + for (to, from) in test_msgs { + client.send_message(to); + server.send_message(from); + + loop_for(Duration::from_millis(250), || ()); + + assert_eq!(client.new_messages().next().unwrap(), from); + assert_eq!(server.new_messages().next().unwrap(), to); + } + } +} diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index ab2947a416..507c009aea 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -137,4 +137,8 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> { pub fn key_pos(&self, key: Vec3<i32>) -> Vec3<i32> { key * S::SIZE.map(|e| e as i32) } + + pub fn pos_key(&self, pos: Vec3<i32>) -> Vec3<i32> { + Self::chunk_key(pos) + } } diff --git a/server/src/client.rs b/server/src/client.rs index 71d8aae6ab..c0e2eafe90 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -21,7 +21,7 @@ pub struct Client { impl Client { pub fn notify(&mut self, msg: ServerMsg) { - self.postbox.send(msg); + self.postbox.send_message(msg); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 9313505a71..f8ebba4b3c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -151,7 +151,7 @@ impl Server { self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { key, - chunk: chunk.clone(), + chunk: Box::new(chunk.clone()), }); } @@ -176,7 +176,7 @@ impl Server { fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> { let mut frontend_events = Vec::new(); - for mut postbox in self.postoffice.new_connections() { + for mut postbox in self.postoffice.new_postboxes() { let entity = self.state .ecs_mut() .create_entity_synced() @@ -245,7 +245,7 @@ impl Server { ClientState::Connected => match msg { ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected ClientMsg::Disconnect => disconnect = true, - ClientMsg::Ping => client.postbox.send(ServerMsg::Pong), + ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong), ClientMsg::Pong => {}, ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), ClientMsg::PlayerPhysics { pos, vel, dir } => { @@ -254,9 +254,9 @@ impl Server { state.write_component(entity, dir); }, ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) { - Some(chunk) => client.postbox.send(ServerMsg::TerrainChunkUpdate { + Some(chunk) => client.postbox.send_message(ServerMsg::TerrainChunkUpdate { key, - chunk: chunk.clone(), + chunk: Box::new(chunk.clone()), }), None => requested_chunks.push(key), }, @@ -270,7 +270,7 @@ impl Server { disconnect = true; } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 { // Try pinging the client if the timeout is nearing - client.postbox.send(ServerMsg::Ping); + client.postbox.send_message(ServerMsg::Ping); } if disconnect { From 5644238d5675ca1e0d852b5db73805f129ab3a64 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 10:09:14 +0100 Subject: [PATCH 06/16] Added partial chunk loading, package section to workspace Cargo.toml Former-commit-id: 4164daf4ccbe6695f664a44d0fce0ee10df39b8d --- Cargo.toml | 6 ++++++ client/src/lib.rs | 4 ++++ common/src/volumes/vol_map.rs | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 533a0add9c..437f7f5e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,9 @@ +[package] +name = "veloren" +description = "Veloren is an open-world, open-source multiplayer voxel RPG." +documentation = "https://docs.veloren.net" +repository = "https://www.gitlab.com/veloren/veloren" + [workspace] members = [ "common", diff --git a/client/src/lib.rs b/client/src/lib.rs index 1d5bdc1a00..c98a01b746 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -144,6 +144,10 @@ impl Client { // Handle new messages from the server frontend_events.append(&mut self.handle_new_messages()?); + self.state.terrain().iter().for_each(|(k, _)| { + println!("Chunk at {:?}", k); + }); + // Step 1 if let Some(ecs_entity) = self.player { // TODO: remove this diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index 507c009aea..fcf45d32cd 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -141,4 +141,22 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> { pub fn pos_key(&self, pos: Vec3<i32>) -> Vec3<i32> { Self::chunk_key(pos) } + + pub fn iter<'a>(&'a self) -> ChunkIter<'a, V, S, M> { + ChunkIter { + iter: self.chunks.iter(), + } + } +} + +pub struct ChunkIter<'a, V: Vox, S: VolSize, M> { + iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Chunk<V, S, M>>, +} + +impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkIter<'a, V, S, M> { + type Item = (Vec3<i32>, &'a Chunk<V, S, M>); + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next().map(|(k, c)| (*k, c)) + } } From c878557056ca0416cd47a63d69fac88d8fd7851e Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 10:10:01 +0100 Subject: [PATCH 07/16] Disabled informing clients of chunks temporarily Former-commit-id: ab964ebfdae55e1fcf1002d7e7622d58e538c83b --- server/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index f8ebba4b3c..8f9c7d5b80 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -149,10 +149,12 @@ impl Server { // TODO: Distance check // if self.state.terrain().key_pos(key) + /* self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { key, chunk: Box::new(chunk.clone()), }); + */ } self.state.insert_chunk(key, chunk); @@ -254,10 +256,10 @@ impl Server { state.write_component(entity, dir); }, ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) { - Some(chunk) => client.postbox.send_message(ServerMsg::TerrainChunkUpdate { + Some(chunk) => {}, /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate { key, chunk: Box::new(chunk.clone()), - }), + }),*/ None => requested_chunks.push(key), }, }, From d45e518413c617c908c2a972f3caa8de9007821d Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 10:17:59 +0100 Subject: [PATCH 08/16] Updated package versions Former-commit-id: bb0d1efd718ddef598fbb894498a43ad014a11c0 --- Cargo.toml | 1 + chat-cli/Cargo.toml | 2 +- client/Cargo.toml | 2 +- common/Cargo.toml | 2 +- server-cli/Cargo.toml | 2 +- server/Cargo.toml | 2 +- voxygen/Cargo.toml | 2 +- world/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 437f7f5e71..f11e750a92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "veloren" description = "Veloren is an open-world, open-source multiplayer voxel RPG." documentation = "https://docs.veloren.net" repository = "https://www.gitlab.com/veloren/veloren" +version = "0.2.0" [workspace] members = [ diff --git a/chat-cli/Cargo.toml b/chat-cli/Cargo.toml index 61161cd537..90e7c0e5d4 100644 --- a/chat-cli/Cargo.toml +++ b/chat-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-chat-cli" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" diff --git a/client/Cargo.toml b/client/Cargo.toml index bc4284934f..847431764b 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-client" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" diff --git a/common/Cargo.toml b/common/Cargo.toml index 8c82e841a3..1b99516d23 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-common" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mckol363@gmail.com>"] edition = "2018" diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml index d682c8b971..62ad0c6368 100644 --- a/server-cli/Cargo.toml +++ b/server-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-server-cli" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" diff --git a/server/Cargo.toml b/server/Cargo.toml index a5b590a902..4189be7693 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-server" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index d418ec1e55..c8d67c4104 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "voxygen" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" diff --git a/world/Cargo.toml b/world/Cargo.toml index e24048bcdd..6918256955 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "veloren-world" -version = "0.1.0" +version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" From 49732f5361bd58cbda415b772a6296cdd96f4545 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 10:22:37 +0100 Subject: [PATCH 09/16] Named Voxygen correctly Former-commit-id: a4f4e5703103e3f93da87bd515e80eb430eef52e --- Cargo.toml | 7 ------- voxygen/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f11e750a92..533a0add9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,3 @@ -[package] -name = "veloren" -description = "Veloren is an open-world, open-source multiplayer voxel RPG." -documentation = "https://docs.veloren.net" -repository = "https://www.gitlab.com/veloren/veloren" -version = "0.2.0" - [workspace] members = [ "common", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index c8d67c4104..9af01232bb 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "voxygen" +name = "veloren-voxygen" version = "0.2.0" authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"] edition = "2018" From de8a6b3c1e9a078ea8ddc0d7d7ed0ded87a70be7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 14:16:44 +0100 Subject: [PATCH 10/16] Prevented postal workers spinning for too long Former-commit-id: e535f5745c817b7fd5f109711aaa65e6c78d6ecc --- common/src/net/post2.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index 16a9bb985a..c82e3b14c6 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -174,7 +174,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { } // Try getting messages from the send channel - loop { + for _ in 0..10 { match send_rx.try_recv() { Ok(Some(send_msg)) => { // Serialize message @@ -206,7 +206,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { } // Try sending bytes through the TCP stream - loop { + for _ in 0..10 { //println!("HERE! Outgoing len: {}", outgoing_chunks.len()); match outgoing_chunks.pop_front() { Some(chunk) => match stream.write_all(&chunk) { @@ -227,7 +227,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { } // Try receiving bytes from the TCP stream - loop { + for _ in 0..10 { let mut buf = [0; 1024]; match stream.read(&mut buf) { @@ -242,7 +242,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { } // Try turning bytes into messages - loop { + for _ in 0..10 { match incoming_buf.get(0..8) { Some(len_bytes) => { let len = usize::from_le_bytes(<[u8; 8]>::try_from(len_bytes).unwrap()); // Can't fail From 86ab128b158943e642c94f78eda366c493303aae Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 14:36:52 +0100 Subject: [PATCH 11/16] Extended timeout on huge transfer test to avoid failure on debug Former-commit-id: 7fa49db2ce310ff83b0c7ba2c4525771f16e7842 --- common/src/net/post2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index c82e3b14c6..62f95078eb 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -365,7 +365,7 @@ mod tests { } let mut recv_msgs = Vec::new(); - loop_for(Duration::from_millis(2000), || server + loop_for(Duration::from_millis(3000), || server .new_messages() .for_each(|msg| recv_msgs.push(msg))); From a9c2b24ed9aa39704ae7d592df59e8d2e01edfa5 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 17:03:42 +0100 Subject: [PATCH 12/16] Fixed 1-frame animation bug Former-commit-id: fc591de5026e35cab1ef72ce250b19cbe2fcec0e --- voxygen/src/scene/figure.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index e0c5ae6ba4..5b61c7ec32 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -91,9 +91,9 @@ impl Figures { .entry(entity) .or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new())); - state.update(renderer, pos.0, dir.0); - RunAnimation::update_skeleton(&mut state.skeleton, time); + + state.update(renderer, pos.0, dir.0); } self.states.retain(|entity, _| ecs.entities().is_alive(*entity)); From 0f5afd1f9240b447e2f9cfeaa4697af0a5f1a56a Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 17:17:14 +0100 Subject: [PATCH 13/16] Fixed zombie network worker threads Former-commit-id: a8e74a9a8e42a0d5e1a79d87a1daf398e6294c73 --- common/src/net/post2.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index 62f95078eb..25db57288b 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -3,7 +3,7 @@ use std::{ net::{TcpListener, TcpStream, SocketAddr, Shutdown}, time::{Instant, Duration}, marker::PhantomData, - sync::mpsc, + sync::{mpsc, Arc, atomic::{AtomicBool, Ordering}}, thread, collections::VecDeque, convert::TryFrom, @@ -85,9 +85,10 @@ impl<S: PostMsg, R: PostMsg> PostOffice<S, R> { } pub struct PostBox<S: PostMsg, R: PostMsg> { - send_tx: mpsc::Sender<Option<S>>, + send_tx: mpsc::Sender<S>, recv_rx: mpsc::Receiver<Result<R, Error>>, worker: Option<thread::JoinHandle<()>>, + running: Arc<AtomicBool>, error: Option<Error>, } @@ -99,15 +100,19 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { fn from_stream(stream: TcpStream) -> Result<Self, Error> { stream.set_nonblocking(true)?; + let running = Arc::new(AtomicBool::new(true)); + let worker_running = running.clone(); + let (send_tx, send_rx) = mpsc::channel(); let (recv_tx, recv_rx) = mpsc::channel(); - let worker = thread::spawn(move || Self::worker(stream, send_rx, recv_tx)); + let worker = thread::spawn(move || Self::worker(stream, send_rx, recv_tx, worker_running)); Ok(Self { send_tx, recv_rx, worker: Some(worker), + running, error: None, }) } @@ -117,7 +122,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { } pub fn send_message(&mut self, msg: S) { - let _ = self.send_tx.send(Some(msg)); + let _ = self.send_tx.send(msg); } pub fn next_message(&mut self) -> Option<R> { @@ -159,11 +164,11 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { new.into_iter() } - fn worker(mut stream: TcpStream, send_rx: mpsc::Receiver<Option<S>>, recv_tx: mpsc::Sender<Result<R, Error>>) { + fn worker(mut stream: TcpStream, send_rx: mpsc::Receiver<S>, recv_tx: mpsc::Sender<Result<R, Error>>, running: Arc<AtomicBool>) { let mut outgoing_chunks = VecDeque::new(); let mut incoming_buf = Vec::new(); - 'work: loop { + 'work: while running.load(Ordering::Relaxed) { // Get stream errors match stream.take_error() { Ok(Some(e)) | Err(e) => { @@ -176,7 +181,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { // Try getting messages from the send channel for _ in 0..10 { match send_rx.try_recv() { - Ok(Some(send_msg)) => { + Ok(send_msg) => { // Serialize message let mut msg_bytes = bincode::serialize(&send_msg).unwrap(); @@ -194,8 +199,6 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { .map(|chunk| chunk.to_vec()) .for_each(|chunk| outgoing_chunks.push_back(chunk)) }, - // Shut down worker - Ok(None) => break 'work, Err(mpsc::TryRecvError::Empty) => break, // Worker error Err(e) => { @@ -276,9 +279,8 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> { impl<S: PostMsg, R: PostMsg> Drop for PostBox<S, R> { fn drop(&mut self) { - let _ = self.send_tx.send(None); - // TODO: Cleanly join! - //self.worker.take().map(|handle| handle.join()); + self.running.store(false, Ordering::Relaxed); + self.worker.take().map(|handle| handle.join()); } } From cb45a079cd74483226cc441e6efae77afc358e54 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 23:19:19 +0100 Subject: [PATCH 14/16] Ensured that Client is Send Former-commit-id: abadfc7d1d86110ed442cdb5a2e639c163c96e04 --- voxygen/src/anim/character/run.rs | 17 +++++++++-------- voxygen/src/anim/mod.rs | 4 +++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 015feb8d89..ac78dca92e 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -24,27 +24,28 @@ impl Animation for RunAnimation { let wave_slow = (time as f32 * 6.0 + PI).sin(); let wave_dip = (wave_slow.abs() - 0.5).abs(); - skeleton.head.offset = Vec3::unit_z() * 13.0; + skeleton.head.offset = Vec3::unit_z() * 13.0 / 11.0; skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); - skeleton.chest.offset = Vec3::unit_z() * 9.0; + skeleton.chest.offset = Vec3::unit_z() * 9.0 / 11.0; skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); - skeleton.belt.offset = Vec3::unit_z() * 7.0; + skeleton.belt.offset = Vec3::unit_z() * 7.0 / 11.0; skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2); - skeleton.shorts.offset = Vec3::unit_z() * 4.0; + skeleton.shorts.offset = Vec3::unit_z() * 4.0 / 11.0; skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1); - skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0); - skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0); + skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0) / 11.0; + skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0) / 11.0; - skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0); + skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0) / 11.0; skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0); - skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0); + skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0) / 11.0; skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0); skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0); skeleton.back.ori = Quaternion::rotation_y(2.5); + skeleton.back.scale = Vec3::one(); } } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index be609f0d59..e226c48e7f 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -10,6 +10,7 @@ use crate::render::FigureBoneData; pub struct Bone { pub offset: Vec3<f32>, pub ori: Quaternion<f32>, + pub scale: Vec3<f32>, } impl Bone { @@ -17,11 +18,12 @@ impl Bone { Self { offset: Vec3::zero(), ori: Quaternion::identity(), + scale: Vec3::broadcast(1.0 / 11.0), } } pub fn compute_base_matrix(&self) -> Mat4<f32> { - Mat4::<f32>::translation_3d(self.offset) * Mat4::from(self.ori) + Mat4::<f32>::translation_3d(self.offset) * Mat4::scaling_3d(self.scale) * Mat4::from(self.ori) } } From a0d32f870c9392fe7f8742da18c1fc5b5c8345e7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Fri, 12 Apr 2019 23:20:08 +0100 Subject: [PATCH 15/16] Fixed player speed Former-commit-id: becc175a91cd60a8957baa8eaaab35f740f677ed --- client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index c98a01b746..a2e3648bea 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -153,7 +153,7 @@ impl Client { // TODO: remove this const PLAYER_VELOCITY: f32 = 100.0; // TODO: Set acceleration instead - self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); + self.state.write_component(ecs_entity, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY) * 0.1)); if input.move_dir.magnitude() > 0.01 { self.state.write_component(ecs_entity, comp::phys::Dir(input.move_dir.normalized().into())); } From 33e0cb54f3832205c752092b75b975ae065b7726 Mon Sep 17 00:00:00 2001 From: Joshua Barretto <joshua.s.barretto@gmail.com> Date: Sat, 13 Apr 2019 23:17:44 +0100 Subject: [PATCH 16/16] Rebased Former-commit-id: 017002bca2078f48998ecb412480492556082491 --- common/src/msg/client.rs | 2 -- common/src/msg/server.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index ffc941b1b6..5a599633d7 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -20,5 +20,3 @@ pub enum ClientMsg { }, Disconnect, } - -impl middleman::Message for ClientMsg {} diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index bdd86945ca..2f852072bf 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -19,5 +19,3 @@ pub enum ServerMsg { chunk: Box<TerrainChunk>, }, } - -impl middleman::Message for ServerMsg {}