Integrated Sphynx

Former-commit-id: 5d96983a385bb77a2876aa7439158252b7e2f0fc
This commit is contained in:
Joshua Barretto 2019-04-10 18:23:27 +01:00
parent bea4a73c2e
commit 62b91eb01b
23 changed files with 317 additions and 344 deletions

View File

@ -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());

View File

@ -3,6 +3,7 @@ use common::net::PostError;
#[derive(Debug)]
pub enum Error {
Network(PostError),
ServerWentMad,
ServerTimeout,
ServerShutdown,
Other(String),

View File

@ -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)
},
}
}

View File

@ -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"] }

View File

@ -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>>;
}

View File

@ -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;

View File

@ -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>>;
}

18
common/src/comp/player.rs Normal file
View File

@ -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>>;
}

View File

@ -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();
}
}

View File

@ -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>;
}

View File

@ -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;

View File

@ -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,
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>),
}

View File

@ -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();

View File

@ -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

View File

@ -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),
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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
))
);
}

View File

@ -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)

View File

@ -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 {