From 2e613178a0a03c31bb2dd338b8a93e1335d2c91c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 4 Mar 2019 19:50:26 +0000 Subject: [PATCH] Component sync + UID system Former-commit-id: 5ecddc0e1f9c1a15f99dd167b825178c972da062 --- client/src/lib.rs | 23 ++++++++++-- common/Cargo.toml | 2 +- common/src/comp/mod.rs | 6 ++-- common/src/comp/phys.rs | 6 ++-- common/src/comp/uid.rs | 32 +++++++++++------ common/src/msg/client.rs | 2 +- common/src/msg/server.rs | 12 +++++-- common/src/state.rs | 33 +++++++++++++++--- server/Cargo.toml | 1 + server/src/client.rs | 29 ++++++++++++++++ server/src/lib.rs | 75 ++++++++++++++++++++++++++++++++-------- 11 files changed, 181 insertions(+), 40 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index af2f41d397..d1496d6a15 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -14,8 +14,9 @@ use std::{ }; use vek::*; use threadpool; +use specs::Builder; use common::{ - comp::phys::Vel, + comp, state::State, terrain::TerrainChunk, net::PostBox, @@ -96,6 +97,13 @@ impl Client { #[allow(dead_code)] pub fn state_mut(&mut self) -> &mut State { &mut self.state } + /// Get an entity from its UID, creating it if it does not exists + pub fn get_or_create_entity(&mut self, uid: u64) -> EcsEntity { + self.state.ecs_world_mut().create_entity() + .with(comp::Uid(uid)) + .build() + } + /// Get the player entity #[allow(dead_code)] pub fn player(&self) -> Option { @@ -141,7 +149,7 @@ impl Client { const PLAYER_VELOCITY: f32 = 100.0; // TODO: Set acceleration instead - self.state.write_component(p, Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); + self.state.write_component(p, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY))); } // Tick the client's LocalState (step 3) @@ -170,11 +178,20 @@ impl Client { self.last_ping = self.state.get_time(); for msg in new_msgs { + println!("Received message"); match msg { - ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::Shutdown => return Err(Error::ServerShutdown), + ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), + ServerMsg::EntityPhysics { uid, pos, vel, dir } => { + let ecs_entity = self.get_or_create_entity(uid); + self.state.write_component(ecs_entity, pos); + self.state.write_component(ecs_entity, vel); + self.state.write_component(ecs_entity, dir); + }, } } + } else if let Some(err) = self.postbox.status() { + return Err(err.into()); } Ok(frontend_events) diff --git a/common/Cargo.toml b/common/Cargo.toml index b8db18d3b3..ca660532d1 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] specs = { version = "0.14", features = ["serde"] } shred = "0.7" -vek = "0.9" +vek = { version = "0.9", features = ["serde"] } dot_vox = "1.0" threadpool = "1.7" mio = "0.6" diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index d822941454..528f1a3468 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,11 +1,13 @@ pub mod phys; pub mod uid; -// External +// Reexports +pub use uid::{Uid, UidAllocator}; + use specs::World as EcsWorld; pub fn register_local_components(ecs_world: &mut EcsWorld) { - ecs_world.register::(); + ecs_world.register::(); ecs_world.register::(); ecs_world.register::(); diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 50809815ba..81eb09a249 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -4,7 +4,7 @@ use vek::*; // Pos -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Pos(pub Vec3); impl Component for Pos { @@ -13,7 +13,7 @@ impl Component for Pos { // Vel -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Vel(pub Vec3); impl Component for Vel { @@ -22,7 +22,7 @@ impl Component for Vel { // Dir -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Dir(pub Vec3); impl Component for Dir { diff --git a/common/src/comp/uid.rs b/common/src/comp/uid.rs index 3f46979036..b28c1cb136 100644 --- a/common/src/comp/uid.rs +++ b/common/src/comp/uid.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, ops::Range, + u64, }; use specs::{ saveload::{Marker, MarkerAllocator}, @@ -13,9 +14,12 @@ use specs::{ }; #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct Uid { - id: u64, - seq: u64, +pub struct Uid(pub u64); + +impl Into for Uid { + fn into(self) -> u64 { + self.0 + } } impl Component for Uid { @@ -24,22 +28,30 @@ impl Component for Uid { impl Marker for Uid { type Identifier = u64; - type Allocator = UidNode; + type Allocator = UidAllocator; - fn id(&self) -> u64 { self.id } + fn id(&self) -> u64 { self.0 } fn update(&mut self, update: Self) { - assert_eq!(self.id, update.id); - self.seq = update.seq; + assert_eq!(self.0, update.0); } } -pub struct UidNode { +pub struct UidAllocator { pub(crate) range: Range, pub(crate) mapping: HashMap, } -impl MarkerAllocator for UidNode { +impl UidAllocator { + pub fn new() -> Self { + Self { + range: 0..u64::MAX, + mapping: HashMap::new(), + } + } +} + +impl MarkerAllocator for UidAllocator { fn allocate(&mut self, entity: Entity, id: Option) -> Uid { let id = id.unwrap_or_else(|| { self.range.next().expect(" @@ -49,7 +61,7 @@ impl MarkerAllocator for UidNode { ") }); self.mapping.insert(id, entity); - Uid { id, seq: 0 } + Uid(id) } fn retrieve_entity_internal(&self, id: u64) -> Option { diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 11d7155c2e..2e304e20bd 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum ClientMsg { Chat(String), Disconnect, diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index def24a97a4..ea80e1038f 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -1,5 +1,13 @@ -#[derive(Debug, Serialize, Deserialize)] +use crate::comp::phys; + +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum ServerMsg { - Chat(String), Shutdown, + Chat(String), + EntityPhysics { + uid: u64, + pos: phys::Pos, + vel: phys::Vel, + dir: phys::Dir, + }, } diff --git a/common/src/state.rs b/common/src/state.rs index 1d557cc846..2e516759e8 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -3,7 +3,17 @@ use std::time::Duration; // Library use shred::{Fetch, FetchMut}; -use specs::{Builder, Component, DispatcherBuilder, Entity as EcsEntity, World as EcsWorld}; +use specs::{ + Builder, + Component, + DispatcherBuilder, + Entity as EcsEntity, + World as EcsWorld, + storage::{ + Storage as EcsStorage, + MaskedStorage as EcsMaskedStorage, + }, +}; use vek::*; // Crate @@ -95,9 +105,19 @@ impl State { .build() } - /// Write a component - pub fn write_component(&mut self, e: EcsEntity, c: C) { - let _ = self.ecs_world.write_storage().insert(e, c); + /// Write a component attributed to a particular entity + pub fn write_component(&mut self, entity: EcsEntity, comp: C) { + let _ = self.ecs_world.write_storage().insert(entity, comp); + } + + /// Read a clone of a component attributed to a particular entity + pub fn read_component(&self, entity: EcsEntity) -> Option { + self.ecs_world.read_storage::().get(entity).cloned() + } + + /// Get a read-only reference to the storage of a particular component type + pub fn read_storage(&self) -> EcsStorage>> { + self.ecs_world.read_storage::() } /// Get a reference to the internal ECS world @@ -105,6 +125,11 @@ impl State { &self.ecs_world } + /// Get a mutable reference to the internal ECS world + pub fn ecs_world_mut(&mut self) -> &mut EcsWorld { + &mut self.ecs_world + } + /// Get a reference to the `Changes` structure of the state. This contains /// information about state that has changed since the last game tick. pub fn changes(&self) -> &Changes { diff --git a/server/Cargo.toml b/server/Cargo.toml index a5950b0420..f7f9f31dcb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -9,3 +9,4 @@ common = { package = "veloren-common", path = "../common" } world = { package = "veloren-world", path = "../world" } specs = "0.14" +vek = "0.9" diff --git a/server/src/client.rs b/server/src/client.rs index 45d42dda73..6d7fd6d660 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -3,9 +3,38 @@ use common::{ msg::{ServerMsg, ClientMsg}, net::PostBox, }; +use crate::Error; pub struct Client { pub ecs_entity: EcsEntity, pub postbox: PostBox, pub last_ping: f64, } + +pub struct Clients { + clients: Vec, +} + +impl Clients { + pub fn empty() -> Self { + Self { + clients: Vec::new(), + } + } + + pub fn add(&mut self, client: Client) { + self.clients.push(client); + } + + pub fn remove_if bool>(&mut self, f: F) { + self.clients.drain_filter(f); + } + + pub fn notify_all(&mut self, msg: ServerMsg) { + for client in &mut self.clients { + // Consume any errors, deal with them later + let _ = client.postbox.send(msg.clone()); + println!("Sending message..."); + } + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index fa5eb309ed..d81a40cb0a 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -14,14 +14,25 @@ use std::{ time::Duration, net::SocketAddr, }; -use specs::Entity as EcsEntity; +use specs::{ + Entity as EcsEntity, + world::EntityBuilder as EcsEntityBuilder, + Builder, + join::Join, + saveload::MarkedBuilder, +}; +use vek::*; use common::{ + comp, state::State, net::PostOffice, msg::{ServerMsg, ClientMsg}, }; use world::World; -use crate::client::Client; +use crate::client::{ + Client, + Clients, +}; const CLIENT_TIMEOUT: f64 = 5.0; // Seconds @@ -43,19 +54,23 @@ pub struct Server { world: World, postoffice: PostOffice, - clients: Vec, + clients: Clients, } impl Server { /// Create a new `Server`. #[allow(dead_code)] pub fn new() -> Result { + let mut state = State::new(); + + state.ecs_world_mut().add_resource(comp::UidAllocator::new()); + Ok(Self { - state: State::new(), + state, world: World::new(), postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?, - clients: Vec::new(), + clients: Clients::empty(), }) } @@ -66,6 +81,20 @@ impl Server { #[allow(dead_code)] pub fn state_mut(&mut self) -> &mut State { &mut self.state } + /// Build a new entity with a generated UID + pub fn build_entity(&mut self) -> EcsEntityBuilder { + self.state.ecs_world_mut().create_entity() + .marked::() + } + + /// Build a new player with a generated UID + pub fn build_player(&mut self) -> EcsEntityBuilder { + self.build_entity() + .with(comp::phys::Pos(Vec3::zero())) + .with(comp::phys::Vel(Vec3::zero())) + .with(comp::phys::Dir(Vec3::unit_y())) + } + /// Get a reference to the server's world. #[allow(dead_code)] pub fn world(&self) -> &World { &self.world } @@ -107,6 +136,9 @@ impl Server { // Tick the client's LocalState (step 3) self.state.tick(dt); + // Synchronise clients with the new state of the world + self.sync_clients(); + // Finish the tick, pass control back to the frontend (step 6) Ok(frontend_events) } @@ -123,14 +155,14 @@ impl Server { let mut frontend_events = Vec::new(); for postbox in self.postoffice.new_connections() { - // TODO: Don't use this method - let ecs_entity = self.state.new_test_player(); + let ecs_entity = self.build_player() + .build(); frontend_events.push(Event::ClientConnected { ecs_entity, }); - self.clients.push(Client { + self.clients.add(Client { ecs_entity, postbox, last_ping: self.state.get_time(), @@ -147,7 +179,7 @@ impl Server { let state = &mut self.state; let mut new_chat_msgs = Vec::new(); - self.clients.drain_filter(|client| { + self.clients.remove_if(|client| { let mut disconnected = false; let new_msgs = client.postbox.new_messages(); @@ -163,8 +195,8 @@ impl Server { } } } else if - state.get_time() - client.last_ping > CLIENT_TIMEOUT || - client.postbox.status().is_some() + state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout + client.postbox.status().is_some() // Postbox eror { disconnected = true; } @@ -182,9 +214,7 @@ impl Server { // Handle new chat messages for (ecs_entity, msg) in new_chat_msgs { - for client in &mut self.clients { - let _ = client.postbox.send(ServerMsg::Chat(msg.clone())); - } + self.clients.notify_all(ServerMsg::Chat(msg.clone())); frontend_events.push(Event::Chat { ecs_entity, @@ -194,4 +224,21 @@ impl Server { Ok(frontend_events) } + + /// Sync client states with the most up to date information + fn sync_clients(&mut self) { + for (&uid, &pos, &vel, &dir) in ( + &self.state.ecs_world().read_storage::(), + &self.state.ecs_world().read_storage::(), + &self.state.ecs_world().read_storage::(), + &self.state.ecs_world().read_storage::(), + ).join() { + self.clients.notify_all(ServerMsg::EntityPhysics { + uid: uid.into(), + pos, + vel, + dir, + }); + } + } }