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 std::time::Duration;
use log::info; use log::info;
use client::{Input, Client, Event}; use client::{Input, Client, Event};
use common::clock::Clock; use common::{
comp,
clock::Clock,
};
const FPS: u64 = 60; const FPS: u64 = 60;
@ -15,7 +18,7 @@ fn main() {
let mut clock = Clock::new(); let mut clock = Clock::new();
// Create client // 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"); .expect("Failed to create client instance");
client.send_chat("Hello!".to_string()); client.send_chat("Hello!".to_string());

View File

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

View File

@ -50,11 +50,31 @@ pub struct Client {
impl Client { impl Client {
/// Create a new `Client`. /// Create a new `Client`.
#[allow(dead_code)] #[allow(dead_code)]
pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> { pub fn new<A: Into<SocketAddr>>(
let state = State::new(); addr: A,
player: comp::Player,
character: Option<comp::Character>,
) -> Result<Self, Error> {
let mut postbox = PostBox::to_server(addr)?; 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 { Ok(Self {
thread_pool: threadpool::Builder::new() thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into()) .thread_name("veloren-worker".into())
@ -65,7 +85,7 @@ impl Client {
tick: 0, tick: 0,
state, state,
player: None, player,
// Testing // Testing
world: World::new(), world: World::new(),
@ -85,12 +105,6 @@ impl Client {
self 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. /// Get a reference to the client's game state.
#[allow(dead_code)] #[allow(dead_code)]
pub fn state(&self) -> &State { &self.state } pub fn state(&self) -> &State { &self.state }
@ -187,28 +201,15 @@ impl Client {
for msg in new_msgs { for msg in new_msgs {
match msg { match msg {
ServerMsg::Handshake { .. } => return Err(Error::ServerWentMad),
ServerMsg::Shutdown => return Err(Error::ServerShutdown), ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::Ping => self.postbox.send(ClientMsg::Pong), ServerMsg::Ping => self.postbox.send(ClientMsg::Pong),
ServerMsg::Pong => {}, ServerMsg::Pong => {},
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)), ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
ServerMsg::SetPlayerEntity(uid) => { ServerMsg::SetPlayerEntity(uid) => self.player = Some(self.state.ecs().entity_from_uid(uid).unwrap()), // TODO: Don't unwrap here!
let ecs_entity = self.state ServerMsg::EcsSync(sync_package) => {
.get_entity(uid) println!("SYNC PACKAGE! {:?}", sync_package);
.unwrap_or_else(|| self.state.build_uid_entity_with_uid(uid).build()); self.state.ecs_mut().sync_with_package(sync_package)
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);
}, },
} }
} }

View File

@ -5,6 +5,8 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc
edition = "2018" edition = "2018"
[dependencies] [dependencies]
sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"] }
specs = { version = "0.14", features = ["serde"] } specs = { version = "0.14", features = ["serde"] }
shred = { version = "0.7", features = ["nightly"] } shred = { version = "0.7", features = ["nightly"] }
vek = { version = "0.9", features = ["serde"] } vek = { version = "0.9", features = ["serde"] }

View File

@ -1,9 +1,8 @@
// Library use specs::{Component, VecStorage, FlaggedStorage};
use specs::{Component, VecStorage};
use vek::*; use vek::*;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
enum Race { pub enum Race {
Danari, Danari,
Dwarf, Dwarf,
Elf, Elf,
@ -13,7 +12,7 @@ enum Race {
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Gender { pub enum Gender {
Female, Female,
Male, Male,
Unspecified, Unspecified,
@ -30,6 +29,21 @@ pub struct Character {
feet: (), feet: (),
} }
impl Component for Character { impl Character {
type Storage = VecStorage<Self>; // 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 phys;
pub mod uid;
pub mod util;
// Reexports // Reexports
pub use uid::{Uid, UidAllocator}; pub use character::Character;
pub use player::Player;
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>();
}

View File

@ -1,5 +1,4 @@
// Library use specs::{Component, VecStorage, FlaggedStorage};
use specs::{Component, VecStorage};
use vek::*; use vek::*;
// Pos // Pos
@ -8,7 +7,7 @@ use vek::*;
pub struct Pos(pub Vec3<f32>); pub struct Pos(pub Vec3<f32>);
impl Component for Pos { impl Component for Pos {
type Storage = VecStorage<Self>; type Storage = FlaggedStorage<Self, VecStorage<Self>>;
} }
// Vel // Vel
@ -17,7 +16,7 @@ impl Component for Pos {
pub struct Vel(pub Vec3<f32>); pub struct Vel(pub Vec3<f32>);
impl Component for Vel { impl Component for Vel {
type Storage = VecStorage<Self>; type Storage = FlaggedStorage<Self, VecStorage<Self>>;
} }
// Dir // Dir
@ -26,17 +25,5 @@ impl Component for Vel {
pub struct Dir(pub Vec3<f32>); pub struct Dir(pub Vec3<f32>);
impl Component for Dir { impl Component for Dir {
type Storage = VecStorage<Self>; type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
// UpdateKind
#[derive(Copy, Clone, Debug)]
pub enum UpdateKind {
Passive,
Force,
}
impl Component for UpdateKind {
type Storage = 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] #[macro_use]
extern crate serde_derive; extern crate serde_derive;

View File

@ -1,17 +1,18 @@
use crate::comp::{ use crate::comp;
Uid,
phys,
};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ClientMsg { pub enum ClientMsg {
Connect {
player: comp::Player,
character: Option<comp::Character>,
},
Ping, Ping,
Pong, Pong,
Chat(String), Chat(String),
PlayerPhysics { PlayerPhysics {
pos: phys::Pos, pos: comp::phys::Pos,
vel: phys::Vel, vel: comp::phys::Vel,
dir: phys::Dir, dir: comp::phys::Dir,
}, },
Disconnect, 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 server;
pub mod client; pub mod client;
// Reexports // Reexports
pub use server::ServerMsg; pub use self::server::ServerMsg;
pub use client::ClientMsg; pub use self::client::ClientMsg;
pub use self::ecs_packet::EcsPacket;

View File

@ -1,20 +1,15 @@
use crate::comp::{ use super::EcsPacket;
Uid,
phys,
};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ServerMsg { pub enum ServerMsg {
Handshake {
ecs_state: sphynx::StatePackage<EcsPacket>,
player_entity: u64,
},
Shutdown, Shutdown,
Ping, Ping,
Pong, Pong,
Chat(String), Chat(String),
SetPlayerEntity(Uid), SetPlayerEntity(u64),
EntityPhysics { EcsSync(sphynx::SyncPackage<EcsPacket>),
uid: Uid,
pos: phys::Pos,
vel: phys::Vel,
dir: phys::Dir,
},
EntityDeleted(Uid),
} }

View File

@ -251,6 +251,45 @@ impl<S: PostSend, R: PostRecv> PostBox<S, R> {
let _ = self.send_tx.send(data); 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> { pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item=R> {
let mut msgs = VecDeque::new(); let mut msgs = VecDeque::new();

View File

@ -6,18 +6,19 @@ use specs::{
DispatcherBuilder, DispatcherBuilder,
EntityBuilder as EcsEntityBuilder, EntityBuilder as EcsEntityBuilder,
Entity as EcsEntity, Entity as EcsEntity,
World as EcsWorld,
storage::{ storage::{
Storage as EcsStorage, Storage as EcsStorage,
MaskedStorage as EcsMaskedStorage, MaskedStorage as EcsMaskedStorage,
}, },
saveload::{MarkedBuilder, MarkerAllocator}, saveload::{MarkedBuilder, MarkerAllocator},
}; };
use sphynx;
use vek::*; use vek::*;
use crate::{ use crate::{
comp, comp,
sys, sys,
terrain::TerrainMap, terrain::TerrainMap,
msg::EcsPacket,
}; };
/// How much faster should an in-game day be compared to a real day? /// 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 /// 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. /// things like entity components, terrain data, and global state like weather, time of day, etc.
pub struct State { pub struct State {
ecs_world: EcsWorld, ecs: sphynx::World<EcsPacket>,
changes: Changes, changes: Changes,
} }
impl State { impl State {
/// Create a new `State`. /// Create a new `State`.
pub fn new() -> Self { 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 { Self {
ecs_world, ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
changes: Changes::default(), 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 /// Register a component with the state's ECS
pub fn with_component<T: Component>(mut self) -> Self pub fn with_component<T: Component>(mut self) -> Self
where <T as Component>::Storage: Default where <T as Component>::Storage: Default
{ {
self.ecs_world.register::<T>(); self.ecs.internal_mut().register::<T>();
self 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 /// Write a component attributed to a particular entity
pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: C) { 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 /// 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>>> { 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 /// Get a reference to the internal ECS world
pub fn ecs_world(&self) -> &EcsWorld { pub fn ecs(&self) -> &sphynx::World<EcsPacket> {
&self.ecs_world &self.ecs
} }
/// Get a mutable reference to the internal ECS world /// Get a mutable reference to the internal ECS world
pub fn ecs_world_mut(&mut self) -> &mut EcsWorld { pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsPacket> {
&mut self.ecs_world &mut self.ecs
} }
/// Get a reference to the `Changes` structure of the state. This contains /// Get a reference to the `Changes` structure of the state. This contains
@ -156,54 +136,46 @@ impl State {
&self.changes &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. /// Get the current in-game time of day.
/// ///
/// Note that this should not be used for physics, animations or other such localised timings. /// Note that this should not be used for physics, animations or other such localised timings.
pub fn get_time_of_day(&self) -> f64 { 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. /// Get the current in-game time.
/// ///
/// Note that this does not correspond to the time of day. /// Note that this does not correspond to the time of day.
pub fn get_time(&self) -> f64 { 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. /// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainMap> { 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 // TODO: Get rid of this since it shouldn't be needed
pub fn terrain_mut(&mut self) -> FetchMut<TerrainMap> { 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. /// Execute a single tick, simulating the game state by the given duration.
pub fn tick(&mut self, dt: 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 // Change the time accordingly
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR; self.ecs.internal_mut().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::<Time>().0 += dt.as_secs_f64();
// Run systems to update the world // 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 // Create and run dispatcher for ecs systems
let mut dispatch_builder = DispatcherBuilder::new(); let mut dispatch_builder = DispatcherBuilder::new();
sys::add_local_systems(&mut dispatch_builder); sys::add_local_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel // 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 /// Clean up the state after a tick

View File

@ -24,9 +24,9 @@ fn main() {
for event in events { for event in events {
match event { match event {
Event::ClientConnected { ecs_entity } => info!("Client connected!"), Event::ClientConnected { entity } => info!("Client connected!"),
Event::ClientDisconnected { ecs_entity } => info!("Client disconnected!"), Event::ClientDisconnected { entity } => info!("Client disconnected!"),
Event::Chat { ecs_entity, msg } => info!("[Client] {}", msg), Event::Chat { entity, msg } => info!("[Client] {}", msg),
} }
} }

View File

@ -6,12 +6,25 @@ use common::{
}; };
use crate::Error; use crate::Error;
#[derive(PartialEq)]
pub enum ClientState {
Connecting,
Connected,
}
pub struct Client { pub struct Client {
pub ecs_entity: EcsEntity, pub state: ClientState,
pub entity: EcsEntity,
pub postbox: PostBox<ServerMsg, ClientMsg>, pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64, pub last_ping: f64,
} }
impl Client {
pub fn notify(&mut self, msg: ServerMsg) {
self.postbox.send(msg);
}
}
pub struct Clients { pub struct Clients {
clients: Vec<Client>, clients: Vec<Client>,
} }
@ -31,15 +44,17 @@ impl Clients {
self.clients.drain_filter(f); 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 { for client in &mut self.clients {
if client.state == ClientState::Connected {
client.postbox.send(msg.clone()); 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 { 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()); client.postbox.send(msg.clone());
} }
} }

View File

@ -30,6 +30,7 @@ use common::{
}; };
use world::World; use world::World;
use crate::client::{ use crate::client::{
ClientState,
Client, Client,
Clients, Clients,
}; };
@ -38,13 +39,13 @@ const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
pub enum Event { pub enum Event {
ClientConnected { ClientConnected {
ecs_entity: EcsEntity, entity: EcsEntity,
}, },
ClientDisconnected { ClientDisconnected {
ecs_entity: EcsEntity, entity: EcsEntity,
}, },
Chat { Chat {
ecs_entity: EcsEntity, entity: EcsEntity,
msg: String, msg: String,
}, },
} }
@ -77,15 +78,6 @@ impl Server {
#[allow(dead_code)] #[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { &mut self.state } 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. /// Get a reference to the server's world.
#[allow(dead_code)] #[allow(dead_code)]
pub fn world(&self) -> &World { &self.world } pub fn world(&self) -> &World { &self.world }
@ -146,23 +138,20 @@ impl Server {
let mut frontend_events = Vec::new(); let mut frontend_events = Vec::new();
for mut postbox in self.postoffice.new_connections() { for mut postbox in self.postoffice.new_connections() {
let ecs_entity = self.build_player() let entity = self.state
// When the player is first created, force a physics notification to everyone .ecs_mut()
// including themselves. .create_entity_synced()
.with(comp::phys::UpdateKind::Force)
.build(); .build();
let uid = self.state.read_storage().get(ecs_entity).cloned().unwrap();
postbox.send(ServerMsg::SetPlayerEntity(uid));
self.clients.add(Client { self.clients.add(Client {
ecs_entity, state: ClientState::Connecting,
entity,
postbox, postbox,
last_ping: self.state.get_time(), last_ping: self.state.get_time(),
}); });
frontend_events.push(Event::ClientConnected { frontend_events.push(Event::ClientConnected {
ecs_entity, entity,
}); });
} }
@ -178,7 +167,7 @@ impl Server {
let mut disconnected_clients = Vec::new(); let mut disconnected_clients = Vec::new();
self.clients.remove_if(|client| { self.clients.remove_if(|client| {
let mut disconnected = false; let mut disconnect = false;
let new_msgs = client.postbox.new_messages(); let new_msgs = client.postbox.new_messages();
// Update client ping // Update client ping
@ -187,30 +176,56 @@ impl Server {
// Process incoming messages // Process incoming messages
for msg in new_msgs { for msg in new_msgs {
match msg { 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::Ping => client.postbox.send(ServerMsg::Pong),
ClientMsg::Pong => {}, ClientMsg::Pong => {},
ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)), ClientMsg::Chat(msg) => new_chat_msgs.push((client.entity, msg)),
ClientMsg::PlayerPhysics { pos, vel, dir } => { ClientMsg::PlayerPhysics { pos, vel, dir } => {
state.write_component(client.ecs_entity, pos); state.write_component(client.entity, pos);
state.write_component(client.ecs_entity, vel); state.write_component(client.entity, vel);
state.write_component(client.ecs_entity, dir); state.write_component(client.entity, dir);
},
ClientMsg::Disconnect => disconnect = true,
}, },
ClientMsg::Disconnect => disconnected = true,
} }
} }
} else if } else if
state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
client.postbox.error().is_some() // Postbox error client.postbox.error().is_some() // Postbox error
{ {
disconnected = true; disconnect = true;
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 { } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 {
// Try pinging the client if the timeout is nearing // Try pinging the client if the timeout is nearing
client.postbox.send(ServerMsg::Ping); client.postbox.send(ServerMsg::Ping);
} }
if disconnected { if disconnect {
disconnected_clients.push(client.ecs_entity); disconnected_clients.push(client.entity);
true true
} else { } else {
false false
@ -218,24 +233,30 @@ impl Server {
}); });
// Handle new chat messages // Handle new chat messages
for (ecs_entity, msg) in new_chat_msgs { for (entity, msg) in new_chat_msgs {
self.clients.notify_all(ServerMsg::Chat(msg.clone())); 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 { frontend_events.push(Event::Chat {
ecs_entity, entity,
msg, msg,
}); });
} }
// Handle client disconnects // Handle client disconnects
for ecs_entity in disconnected_clients { for entity in disconnected_clients {
self.clients.notify_all(ServerMsg::EntityDeleted(state.read_storage().get(ecs_entity).cloned().unwrap())); state.ecs_mut().delete_entity_synced(entity);
frontend_events.push(Event::ClientDisconnected { frontend_events.push(Event::ClientDisconnected {
ecs_entity, entity,
}); });
state.ecs_world_mut().delete_entity(ecs_entity);
} }
Ok(frontend_events) Ok(frontend_events)
@ -243,36 +264,12 @@ impl Server {
/// Sync client states with the most up to date information /// Sync client states with the most up to date information
fn sync_clients(&mut self) { fn sync_clients(&mut self) {
for (entity, &uid, &pos, &vel, &dir, update_kind) in ( self.clients.notify_connected(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
&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;
}
} }
} }
impl Drop for Server { impl Drop for Server {
fn drop(&mut self) { 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, GlobalState, PlayState, PlayStateResult,
}; };
use client::{self, Client}; use client::{self, Client};
use common::clock::Clock; use common::{
comp,
clock::Clock,
};
use std::time::Duration; use std::time::Duration;
use ui::{Event as MainMenuEvent, MainMenuUi}; use ui::{Event as MainMenuEvent, MainMenuUi};
use vek::*; use vek::*;
@ -67,12 +70,12 @@ impl PlayState for MainMenuState {
Ok(mut socket_adders) => { Ok(mut socket_adders) => {
while let Some(socket_addr) = socket_adders.next() { while let Some(socket_addr) = socket_adders.next() {
// TODO: handle error // TODO: handle error
match Client::new(socket_addr) { match Client::new(socket_addr, comp::Player::new(username.clone()), Some(comp::Character::test())) {
Ok(client) => { Ok(client) => {
return PlayStateResult::Push( return PlayStateResult::Push(
Box::new(CharSelectionState::new( Box::new(CharSelectionState::new(
&mut global_state.window, &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() .player()
.and_then(|ent| client .and_then(|ent| client
.state() .state()
.ecs_world() .ecs()
.internal()
.read_storage::<comp::phys::Pos>() .read_storage::<comp::phys::Pos>()
.get(ent) .get(ent)
.map(|pos| pos.0) .map(|pos| pos.0)

View File

@ -105,6 +105,7 @@ impl PlayState for SessionState {
let mut clock = Clock::new(); let mut clock = Clock::new();
// Load a few chunks TODO: Remove this // Load a few chunks TODO: Remove this
/*
for x in -6..7 { for x in -6..7 {
for y in -6..7 { for y in -6..7 {
for z in -1..2 { for z in -1..2 {
@ -112,6 +113,7 @@ impl PlayState for SessionState {
} }
} }
} }
*/
// Game loop // Game loop
loop { loop {