mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Integrated Sphynx
Former-commit-id: 5d96983a385bb77a2876aa7439158252b7e2f0fc
This commit is contained in:
parent
bea4a73c2e
commit
62b91eb01b
@ -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());
|
||||
|
@ -3,6 +3,7 @@ use common::net::PostError;
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Network(PostError),
|
||||
ServerWentMad,
|
||||
ServerTimeout,
|
||||
ServerShutdown,
|
||||
Other(String),
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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"] }
|
||||
|
@ -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>>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
18
common/src/comp/player.rs
Normal 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>>;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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>;
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
|
29
common/src/msg/ecs_packet.rs
Normal file
29
common/src/msg/ecs_packet.rs
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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>),
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
))
|
||||
);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user