Merge branch 'imbris/sync' into 'master'

Fix sync issues

Closes #359 and #377

See merge request veloren/veloren!675
This commit is contained in:
Imbris 2019-12-21 04:26:38 +00:00
commit ddfa2b4600
49 changed files with 1677 additions and 720 deletions

431
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,6 @@ uvth = "3.1.1"
image = "0.22.3" image = "0.22.3"
num_cpus = "1.10.1" num_cpus = "1.10.1"
log = "0.4.8" log = "0.4.8"
specs = "0.14.2" specs = "0.15.1"
vek = { version = "0.9.9", features = ["serde"] } vek = { version = "0.9.9", features = ["serde"] }
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] } hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }

View File

@ -5,7 +5,7 @@ pub mod error;
// Reexports // Reexports
pub use crate::error::Error; pub use crate::error::Error;
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage}; pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage, WorldExt};
use common::{ use common::{
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip}, comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
@ -14,7 +14,8 @@ use common::{
ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
}, },
net::PostBox, net::PostBox,
state::{State, Uid}, state::State,
sync::{Uid, WorldSyncExt},
terrain::{block::Block, TerrainChunk, TerrainChunkSize}, terrain::{block::Block, TerrainChunk, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
ChatType, ChatType,
@ -78,9 +79,9 @@ impl Client {
// Wait for initial sync // Wait for initial sync
let (state, entity, server_info, world_map) = match postbox.next_message() { let (state, entity, server_info, world_map) = match postbox.next_message() {
Some(ServerMsg::InitialSync { Some(ServerMsg::InitialSync {
ecs_state, entity_package,
entity_uid,
server_info, server_info,
time_of_day,
// world_map: /*(map_size, world_map)*/map_size, // world_map: /*(map_size, world_map)*/map_size,
}) => { }) => {
// TODO: Voxygen should display this. // TODO: Voxygen should display this.
@ -94,11 +95,10 @@ impl Client {
); );
} }
let state = State::from_state_package(ecs_state); // Initialize `State`
let entity = state let mut state = State::default();
.ecs() let entity = state.ecs_mut().apply_entity_package(entity_package);
.entity_from_uid(entity_uid) *state.ecs_mut().write_resource() = time_of_day;
.ok_or(Error::ServerWentMad)?;
// assert_eq!(world_map.len(), map_size.x * map_size.y); // assert_eq!(world_map.len(), map_size.x * map_size.y);
let map_size = Vec2::new(1024, 1024); let map_size = Vec2::new(1024, 1024);
@ -537,7 +537,7 @@ impl Client {
self.last_ping_delta = Instant::now() self.last_ping_delta = Instant::now()
.duration_since(self.last_server_ping) .duration_since(self.last_server_ping)
.as_secs_f64() .as_secs_f64();
} }
ServerMsg::ChatMsg { chat_type, message } => { ServerMsg::ChatMsg { chat_type, message } => {
frontend_events.push(Event::Chat { chat_type, message }) frontend_events.push(Event::Chat { chat_type, message })
@ -549,14 +549,25 @@ impl Client {
return Err(Error::Other("Failed to find entity from uid.".to_owned())); return Err(Error::Other("Failed to find entity from uid.".to_owned()));
} }
} }
ServerMsg::TimeOfDay(time_of_day) => {
*self.state.ecs_mut().write_resource() = time_of_day;
}
ServerMsg::EcsSync(sync_package) => { ServerMsg::EcsSync(sync_package) => {
self.state.ecs_mut().sync_with_package(sync_package) self.state.ecs_mut().apply_sync_package(sync_package);
}
ServerMsg::CreateEntity(entity_package) => {
self.state.ecs_mut().apply_entity_package(entity_package);
} }
ServerMsg::DeleteEntity(entity) => { ServerMsg::DeleteEntity(entity) => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) { if self
if entity != self.entity { .state
let _ = self.state.ecs_mut().delete_entity(entity); .read_component_cloned::<Uid>(self.entity)
} .map(|u| u.into())
!= Some(entity)
{
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity);
} }
} }
ServerMsg::EntityPos { entity, pos } => { ServerMsg::EntityPos { entity, pos } => {

View File

@ -5,10 +5,9 @@ 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"], rev = "ac4adf54d181339a789907acd27f61fe81daa455" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
specs = { version = "0.14.2", features = ["serde", "nightly"] } specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
vek = { version = "0.9.9", features = ["serde"] } vek = { version = "0.9.9", features = ["serde"] }
dot_vox = "4.0.0" dot_vox = "4.0.0"
image = "0.22.3" image = "0.22.3"
@ -30,8 +29,7 @@ parking_lot = "0.9.0"
crossbeam = "=0.7.2" crossbeam = "=0.7.2"
notify = "5.0.0-pre.1" notify = "5.0.0-pre.1"
indexmap = "1.3.0" indexmap = "1.3.0"
# TODO: remove when upgrading to specs 0.15 sum_type = "0.2.0"
hibitset = "0.5.3"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View File

@ -33,6 +33,23 @@ impl Body {
_ => false, _ => false,
} }
} }
// Note: this might need to be refined to something more complex for realistic
// behavior with less cylindrical bodies (e.g. wolfs)
pub fn radius(&self) -> f32 {
// TODO: Improve these values (some might be reliant on more info in inner type)
match self {
Body::Humanoid(_) => 0.5,
Body::QuadrupedSmall(_) => 0.6,
Body::QuadrupedMedium(_) => 0.9,
Body::BirdMedium(_) => 0.5,
Body::FishMedium(_) => 0.5,
Body::Dragon(_) => 2.5,
Body::BirdSmall(_) => 0.2,
Body::FishSmall(_) => 0.2,
Body::BipedLarge(_) => 1.0,
Body::Object(_) => 0.3,
}
}
} }
impl Component for Body { impl Component for Body {

View File

@ -1,6 +1,6 @@
use crate::sync::Uid;
use specs::{Component, FlaggedStorage}; use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage; use specs_idvs::IDVStorage;
use sphynx::Uid;
use std::time::Duration; use std::time::Duration;
use vek::*; use vek::*;

View File

@ -1,4 +1,4 @@
use crate::state::Uid; use crate::sync::Uid;
use specs::{Component, FlaggedStorage, NullStorage}; use specs::{Component, FlaggedStorage, NullStorage};
use specs_idvs::IDVStorage; use specs_idvs::IDVStorage;
use vek::*; use vek::*;

View File

@ -1,5 +1,4 @@
use crate::comp; use crate::{comp, sync::Uid};
use crate::state::Uid;
use specs::{Component, FlaggedStorage}; use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage; use specs_idvs::IDVStorage;
use std::time::Duration; use std::time::Duration;
@ -15,6 +14,7 @@ pub enum Effect {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Projectile { pub struct Projectile {
pub owner: Uid, pub owner: Uid,
// TODO: use SmallVec for these effects
pub hit_ground: Vec<Effect>, pub hit_ground: Vec<Effect>,
pub hit_wall: Vec<Effect>, pub hit_wall: Vec<Effect>,
pub hit_entity: Vec<Effect>, pub hit_entity: Vec<Effect>,

View File

@ -1,4 +1,4 @@
use crate::{comp, state::Uid}; use crate::{comp, sync::Uid};
use specs::{Component, FlaggedStorage}; use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage; use specs_idvs::IDVStorage;

View File

@ -1,9 +1,8 @@
use crate::comp; use crate::{comp, sync::Uid};
use comp::item::Tool; use comp::item::Tool;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; use serde::Deserialize;
use specs::Entity as EcsEntity; use specs::Entity as EcsEntity;
use sphynx::Uid;
use std::{collections::VecDeque, ops::DerefMut}; use std::{collections::VecDeque, ops::DerefMut};
use vek::*; use vek::*;

View File

@ -20,6 +20,7 @@ pub mod pathfinding;
pub mod ray; pub mod ray;
pub mod region; pub mod region;
pub mod state; pub mod state;
pub mod sync;
pub mod sys; pub mod sys;
pub mod terrain; pub mod terrain;
pub mod util; pub mod util;

View File

@ -1,25 +1,13 @@
use crate::{comp, state}; use crate::{comp, sync};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
use sum_type::sum_type;
// Automatically derive From<T> for EcsResPacket
// for each variant EcsResPacket::T(T).
sphynx::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsResPacket {
Time(state::Time),
TimeOfDay(state::TimeOfDay),
}
}
impl sphynx::ResPacket for EcsResPacket {}
// Automatically derive From<T> for EcsCompPacket // Automatically derive From<T> for EcsCompPacket
// for each variant EcsCompPacket::T(T.) // for each variant EcsCompPacket::T(T.)
sphynx::sum_type! { sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket { pub enum EcsCompPacket {
Pos(comp::Pos),
Vel(comp::Vel),
Ori(comp::Ori),
Body(comp::Body), Body(comp::Body),
Player(comp::Player), Player(comp::Player),
CanBuild(comp::CanBuild), CanBuild(comp::CanBuild),
@ -30,19 +18,15 @@ sphynx::sum_type! {
MountState(comp::MountState), MountState(comp::MountState),
Mounting(comp::Mounting), Mounting(comp::Mounting),
Mass(comp::Mass), Mass(comp::Mass),
Projectile(comp::Projectile),
Gravity(comp::Gravity), Gravity(comp::Gravity),
Sticky(comp::Sticky), Sticky(comp::Sticky),
} }
} }
// Automatically derive From<T> for EcsCompPhantom // Automatically derive From<T> for EcsCompPhantom
// for each variant EcsCompPhantom::T(PhantomData<T>). // for each variant EcsCompPhantom::T(PhantomData<T>).
sphynx::sum_type! { sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom { pub enum EcsCompPhantom {
Pos(PhantomData<comp::Pos>),
Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>),
Body(PhantomData<comp::Body>), Body(PhantomData<comp::Body>),
Player(PhantomData<comp::Player>), Player(PhantomData<comp::Player>),
CanBuild(PhantomData<comp::CanBuild>), CanBuild(PhantomData<comp::CanBuild>),
@ -53,11 +37,60 @@ sphynx::sum_type! {
MountState(PhantomData<comp::MountState>), MountState(PhantomData<comp::MountState>),
Mounting(PhantomData<comp::Mounting>), Mounting(PhantomData<comp::Mounting>),
Mass(PhantomData<comp::Mass>), Mass(PhantomData<comp::Mass>),
Projectile(PhantomData<comp::Projectile>),
Gravity(PhantomData<comp::Gravity>), Gravity(PhantomData<comp::Gravity>),
Sticky(PhantomData<comp::Sticky>), Sticky(PhantomData<comp::Sticky>),
} }
} }
impl sphynx::CompPacket for EcsCompPacket { impl sync::CompPacket for EcsCompPacket {
type Phantom = EcsCompPhantom; type Phantom = EcsCompPhantom;
fn apply_insert(self, entity: specs::Entity, world: &specs::World) {
match self {
EcsCompPacket::Body(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
}
}
fn apply_modify(self, entity: specs::Entity, world: &specs::World) {
match self {
EcsCompPacket::Body(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
}
}
fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) {
match phantom {
EcsCompPhantom::Body(_) => sync::handle_remove::<comp::Body>(entity, world),
EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
EcsCompPhantom::LightEmitter(_) => {
sync::handle_remove::<comp::LightEmitter>(entity, world)
}
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
}
}
} }

View File

@ -4,7 +4,7 @@ pub mod server;
// Reexports // Reexports
pub use self::client::ClientMsg; pub use self::client::ClientMsg;
pub use self::ecs_packet::{EcsCompPacket, EcsResPacket}; pub use self::ecs_packet::EcsCompPacket;
pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg}; pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View File

@ -1,6 +1,6 @@
use super::{ClientState, EcsCompPacket, EcsResPacket}; use super::{ClientState, EcsCompPacket};
use crate::{ use crate::{
comp, comp, state, sync,
terrain::{Block, TerrainChunk}, terrain::{Block, TerrainChunk},
ChatType, ChatType,
}; };
@ -26,9 +26,9 @@ pub struct ServerInfo {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerMsg { pub enum ServerMsg {
InitialSync { InitialSync {
ecs_state: sphynx::StatePackage<EcsCompPacket, EcsResPacket>, entity_package: sync::EntityPackage<EcsCompPacket>,
entity_uid: u64,
server_info: ServerInfo, server_info: ServerInfo,
time_of_day: state::TimeOfDay,
// world_map: Vec2<usize>, /*, Vec<u32>)*/ // world_map: Vec2<usize>, /*, Vec<u32>)*/
}, },
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>), StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
@ -40,7 +40,9 @@ pub enum ServerMsg {
message: String, message: String,
}, },
SetPlayerEntity(u64), SetPlayerEntity(u64),
EcsSync(sphynx::SyncPackage<EcsCompPacket, EcsResPacket>), TimeOfDay(state::TimeOfDay),
EcsSync(sync::SyncPackage<EcsCompPacket>),
CreateEntity(sync::EntityPackage<EcsCompPacket>),
DeleteEntity(u64), DeleteEntity(u64),
EntityPos { EntityPos {
entity: u64, entity: u64,

View File

@ -1,8 +1,7 @@
use crate::comp::{Pos, Vel}; use crate::comp::{Pos, Vel};
use hashbrown::{hash_map::DefaultHashBuilder, HashSet}; use hashbrown::{hash_map::DefaultHashBuilder, HashSet};
use hibitset::BitSetLike;
use indexmap::IndexMap; use indexmap::IndexMap;
use specs::{BitSet, Entities, Join, ReadStorage}; use specs::{hibitset::BitSetLike, BitSet, Entities, Join, ReadStorage};
use vek::*; use vek::*;
pub enum Event { pub enum Event {
@ -104,6 +103,16 @@ impl RegionMap {
// TODO special case large entities // TODO special case large entities
pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) { pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) {
self.tick += 1; self.tick += 1;
// Clear events within each region
for i in 0..self.regions.len() {
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
}
// Add any untracked entites // Add any untracked entites
for (pos, id) in (&pos, &entities, !&self.tracked_entities) for (pos, id) in (&pos, &entities, !&self.tracked_entities)
.join() .join()
@ -118,14 +127,6 @@ impl RegionMap {
let mut regions_to_remove = Vec::new(); let mut regions_to_remove = Vec::new();
for i in 0..self.regions.len() { for i in 0..self.regions.len() {
// Clear events within each region
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
for (maybe_pos, _maybe_vel, id) in ( for (maybe_pos, _maybe_vel, id) in (
pos.maybe(), pos.maybe(),
vel.maybe(), vel.maybe(),
@ -215,6 +216,47 @@ impl RegionMap {
pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> { pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> {
key.map(|e| e << REGION_LOG2) key.map(|e| e << REGION_LOG2)
} }
/// Finds the region where a given entity is located using a given position to speed up the search
pub fn find_region(&self, entity: specs::Entity, pos: Vec3<f32>) -> Option<Vec2<i32>> {
let id = entity.id();
// Compute key for most likely region
let key = Self::pos_key(pos.map(|e| e as i32));
// Get region
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
} else {
// Check neighbors
for i in 0..8 {
if let Some(idx) = region.neighbors[i] {
let (key, region) = self.regions.get_index(idx).unwrap();
if region.entities().contains(id) {
return Some(*key);
}
}
}
}
} else {
// Check neighbors
for i in 0..8 {
let key = key + NEIGHBOR_OFFSETS[i];
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
}
}
}
}
// Scan though all regions
for (key, region) in self.iter() {
if region.entities().contains(id) {
return Some(key);
}
}
None
}
fn key_index(&self, key: Vec2<i32>) -> Option<usize> { fn key_index(&self, key: Vec2<i32>) -> Option<usize> {
self.regions.get_full(&key).map(|(i, _, _)| i) self.regions.get_full(&key).map(|(i, _, _)| i)
} }

View File

@ -1,11 +1,8 @@
// Reexports
pub use sphynx::Uid;
use crate::{ use crate::{
comp, comp,
event::{EventBus, LocalEvent, ServerEvent, SfxEventItem}, event::{EventBus, LocalEvent, ServerEvent, SfxEventItem},
msg::{EcsCompPacket, EcsResPacket},
region::RegionMap, region::RegionMap,
sync::WorldSyncExt,
sys, sys,
terrain::{Block, TerrainChunk, TerrainGrid}, terrain::{Block, TerrainChunk, TerrainGrid},
vol::WriteVol, vol::WriteVol,
@ -14,12 +11,10 @@ use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use specs::{ use specs::{
saveload::Marker,
shred::{Fetch, FetchMut}, shred::{Fetch, FetchMut},
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage}, storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
Component, DispatcherBuilder, Entity as EcsEntity, Join, Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
}; };
use sphynx;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use vek::*; use vek::*;
@ -28,7 +23,7 @@ use vek::*;
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0; const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// A resource that stores the time of day. /// A resource that stores the time of day.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct TimeOfDay(pub f64); pub struct TimeOfDay(pub f64);
/// A resource that stores the tick (i.e: physics) time. /// A resource that stores the tick (i.e: physics) time.
@ -89,7 +84,7 @@ impl TerrainChanges {
/// 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 states like weather, time of day, etc. /// things like entity components, terrain data, and global states like weather, time of day, etc.
pub struct State { pub struct State {
ecs: sphynx::World<EcsCompPacket, EcsResPacket>, ecs: specs::World,
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher // Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
thread_pool: Arc<ThreadPool>, thread_pool: Arc<ThreadPool>,
} }
@ -98,44 +93,32 @@ impl Default for State {
/// Create a new `State`. /// Create a new `State`.
fn default() -> Self { fn default() -> Self {
Self { Self {
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world), ecs: Self::setup_ecs_world(),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()), thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
} }
} }
} }
impl State { impl State {
/// Create a new `State` from an ECS state package. /// Creates ecs world and registers all the common components and resources
pub fn from_state_package(
state_package: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
) -> Self {
Self {
ecs: sphynx::World::from_state_package(
specs::World::new(),
Self::setup_sphynx_world,
state_package,
),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
}
}
// Create a new Sphynx ECS world.
// TODO: Split up registering into server and client (e.g. move EventBus<ServerEvent> to the server) // TODO: Split up registering into server and client (e.g. move EventBus<ServerEvent> to the server)
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsCompPacket, EcsResPacket>) { fn setup_ecs_world() -> specs::World {
let mut ecs = specs::World::new();
// Uids for sync
ecs.register_sync_marker();
// Register server -> all clients synced components. // Register server -> all clients synced components.
ecs.register_synced::<comp::Body>(); ecs.register::<comp::Body>();
ecs.register_synced::<comp::Player>(); ecs.register::<comp::Player>();
ecs.register_synced::<comp::Stats>(); ecs.register::<comp::Stats>();
ecs.register_synced::<comp::CanBuild>(); ecs.register::<comp::CanBuild>();
ecs.register_synced::<comp::LightEmitter>(); ecs.register::<comp::LightEmitter>();
ecs.register_synced::<comp::Item>(); ecs.register::<comp::Item>();
ecs.register_synced::<comp::Scale>(); ecs.register::<comp::Scale>();
ecs.register_synced::<comp::Mounting>(); ecs.register::<comp::Mounting>();
ecs.register_synced::<comp::MountState>(); ecs.register::<comp::MountState>();
ecs.register_synced::<comp::Mass>(); ecs.register::<comp::Mass>();
ecs.register_synced::<comp::Sticky>(); ecs.register::<comp::Sticky>();
ecs.register_synced::<comp::Gravity>(); ecs.register::<comp::Gravity>();
ecs.register_synced::<comp::Projectile>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();
@ -151,6 +134,7 @@ impl State {
ecs.register::<comp::Inventory>(); ecs.register::<comp::Inventory>();
// Register server-local components // Register server-local components
// TODO: only register on the server
ecs.register::<comp::Last<comp::Pos>>(); ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>(); ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>(); ecs.register::<comp::Last<comp::Ori>>();
@ -158,23 +142,26 @@ impl State {
ecs.register::<comp::Agent>(); ecs.register::<comp::Agent>();
ecs.register::<comp::ForceUpdate>(); ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>(); ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Inventory>();
ecs.register::<comp::Admin>(); ecs.register::<comp::Admin>();
ecs.register::<comp::Waypoint>(); ecs.register::<comp::Waypoint>();
ecs.register::<comp::Projectile>();
// Register synced resources used by the ECS. // Register synced resources used by the ECS.
ecs.insert_synced(TimeOfDay(0.0)); ecs.insert(TimeOfDay(0.0));
// Register unsynced resources used by the ECS. // Register unsynced resources used by the ECS.
ecs.add_resource(Time(0.0)); ecs.insert(Time(0.0));
ecs.add_resource(DeltaTime(0.0)); ecs.insert(DeltaTime(0.0));
ecs.add_resource(TerrainGrid::new().unwrap()); ecs.insert(TerrainGrid::new().unwrap());
ecs.add_resource(BlockChange::default()); ecs.insert(BlockChange::default());
ecs.add_resource(TerrainChanges::default()); ecs.insert(TerrainChanges::default());
ecs.add_resource(EventBus::<ServerEvent>::default()); // TODO: only register on the server
ecs.add_resource(EventBus::<LocalEvent>::default()); ecs.insert(EventBus::<ServerEvent>::default());
ecs.add_resource(EventBus::<SfxEventItem>::default()); ecs.insert(EventBus::<LocalEvent>::default());
ecs.add_resource(RegionMap::new()); ecs.insert(EventBus::<SfxEventItem>::default());
ecs.insert(RegionMap::new());
ecs
} }
/// Register a component with the state's ECS. /// Register a component with the state's ECS.
@ -207,12 +194,12 @@ impl State {
} }
/// Get a reference to the internal ECS world. /// Get a reference to the internal ECS world.
pub fn ecs(&self) -> &sphynx::World<EcsCompPacket, EcsResPacket> { pub fn ecs(&self) -> &specs::World {
&self.ecs &self.ecs
} }
/// Get a mutable reference to the internal ECS world. /// Get a mutable reference to the internal ECS world.
pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsCompPacket, EcsResPacket> { pub fn ecs_mut(&mut self) -> &mut specs::World {
&mut self.ecs &mut self.ecs
} }
@ -319,68 +306,6 @@ impl State {
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping important physics events. // Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping important physics events.
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME); self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
// Mounted entities. We handle this here because we need access to the Uid registry and I
// forgot how to access that within a system. Anyhow, here goes.
for (entity, mount_state) in (
&self.ecs.entities(),
&mut self.ecs.write_storage::<comp::MountState>(),
)
.join()
{
match mount_state {
comp::MountState::Unmounted => {}
comp::MountState::MountedBy(mounter) => {
if let Some((controller, mounter)) =
self.ecs.entity_from_uid(mounter.id()).and_then(|mounter| {
self.ecs
.read_storage::<comp::Controller>()
.get(mounter)
.cloned()
.map(|x| (x, mounter))
})
{
let pos = self.ecs.read_storage::<comp::Pos>().get(entity).copied();
let ori = self.ecs.read_storage::<comp::Ori>().get(entity).copied();
let vel = self.ecs.read_storage::<comp::Vel>().get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let _ = self
.ecs
.write_storage()
.insert(mounter, comp::Pos(pos.0 + Vec3::unit_z() * 1.0));
let _ = self.ecs.write_storage().insert(mounter, ori);
let _ = self.ecs.write_storage().insert(mounter, vel);
}
let _ = self
.ecs
.write_storage::<comp::Controller>()
.insert(entity, controller);
} else {
*mount_state = comp::MountState::Unmounted;
}
}
}
}
let mut to_unmount = Vec::new();
for (entity, comp::Mounting(mountee)) in (
&self.ecs.entities(),
&self.ecs.read_storage::<comp::Mounting>(),
)
.join()
{
if self
.ecs
.entity_from_uid(mountee.id())
.filter(|mountee| self.ecs.is_alive(*mountee))
.is_none()
{
to_unmount.push(entity);
}
}
for entity in to_unmount {
self.ecs.write_storage::<comp::Mounting>().remove(entity);
}
// Run RegionMap tick to update entity region occupancy // Run RegionMap tick to update entity region occupancy
self.ecs.write_resource::<RegionMap>().tick( self.ecs.write_resource::<RegionMap>().tick(
self.ecs.read_storage::<comp::Pos>(), self.ecs.read_storage::<comp::Pos>(),
@ -395,7 +320,7 @@ impl State {
// TODO: Consider alternative ways to do this // TODO: Consider alternative ways to do this
add_foreign_systems(&mut dispatch_builder); add_foreign_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel. // This dispatches all the systems in parallel.
dispatch_builder.build().dispatch(&self.ecs.res); dispatch_builder.build().dispatch(&self.ecs);
self.ecs.maintain(); self.ecs.maintain();

14
common/src/sync/mod.rs Normal file
View File

@ -0,0 +1,14 @@
// Note: Currently only one-way sync is supported until a usecase for two-way sync arises
mod packet;
mod sync_ext;
mod track;
mod uid;
// Reexports
pub use packet::{
handle_insert, handle_modify, handle_remove, CompPacket, EntityPackage, StatePackage,
SyncPackage,
};
pub use sync_ext::WorldSyncExt;
pub use track::UpdateTracker;
pub use uid::{Uid, UidAllocator};

125
common/src/sync/packet.rs Normal file
View File

@ -0,0 +1,125 @@
use super::{track::UpdateTracker, uid::Uid};
use log::error;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use specs::{Component, Entity, Join, ReadStorage, World, WorldExt};
use std::{
convert::{TryFrom, TryInto},
fmt::Debug,
marker::PhantomData,
};
/// Implemented by type that carries component data for insertion and modification
/// The assocatied `Phantom` type only carries information about which component type is of
/// interest and is used to transmit deletion events
pub trait CompPacket: Clone + Debug + Send + 'static {
type Phantom: Clone + Debug + Serialize + DeserializeOwned;
fn apply_insert(self, entity: Entity, world: &World);
fn apply_modify(self, entity: Entity, world: &World);
fn apply_remove(phantom: Self::Phantom, entity: Entity, world: &World);
}
/// Useful for implementing CompPacket trait
pub fn handle_insert<C: Component>(comp: C, entity: Entity, world: &World) {
if let Err(err) = world.write_storage::<C>().insert(entity, comp) {
error!("Error inserting component: {:?}", err);
};
}
/// Useful for implementing CompPacket trait
pub fn handle_modify<C: Component>(comp: C, entity: Entity, world: &World) {
let _ = world
.write_storage::<C>()
.get_mut(entity)
.map(|c| *c = comp);
}
/// Useful for implementing CompPacket trait
pub fn handle_remove<C: Component>(entity: Entity, world: &World) {
let _ = world.write_storage::<C>().remove(entity);
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum CompUpdateKind<P: CompPacket> {
Inserted(P),
Modified(P),
Removed(P::Phantom),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntityPackage<P: CompPacket> {
pub uid: u64,
pub comps: Vec<P>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StatePackage<P: CompPacket> {
pub entities: Vec<EntityPackage<P>>,
}
impl<P: CompPacket> Default for StatePackage<P> {
fn default() -> Self {
Self {
entities: Vec::new(),
}
}
}
impl<P: CompPacket> StatePackage<P> {
pub fn new() -> Self {
Self::default()
}
pub fn with_entities<C: Component + Clone + Send + Sync>(
mut self,
mut entities: Vec<EntityPackage<P>>,
) -> Self {
self.entities.append(&mut entities);
self
}
pub fn with_entity(mut self, entry: EntityPackage<P>) -> Self {
self.entities.push(entry);
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncPackage<P: CompPacket> {
pub comp_updates: Vec<(u64, CompUpdateKind<P>)>,
pub created_entities: Vec<u64>,
pub deleted_entities: Vec<u64>,
}
impl<P: CompPacket> SyncPackage<P> {
pub fn new<'a>(
uids: &ReadStorage<'a, Uid>,
uid_tracker: &UpdateTracker<Uid>,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> Self {
// Add created and deleted entities
let created_entities = (uids, filter, uid_tracker.inserted())
.join()
.map(|(uid, _, _)| (*uid).into())
.collect();
Self {
comp_updates: Vec::new(),
created_entities,
deleted_entities,
}
}
pub fn with_component<'a, C: Component + Clone + Send + Sync>(
mut self,
uids: &ReadStorage<'a, Uid>,
tracker: &UpdateTracker<C>,
storage: &ReadStorage<'a, C>,
filter: impl Join + Copy,
) -> Self
where
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
self
}
}

164
common/src/sync/sync_ext.rs Normal file
View File

@ -0,0 +1,164 @@
use super::{
packet::{CompPacket, CompUpdateKind, EntityPackage, StatePackage, SyncPackage},
track::UpdateTracker,
uid::{Uid, UidAllocator},
};
use log::error;
use specs::{
saveload::{MarkedBuilder, MarkerAllocator},
world::Builder,
WorldExt,
};
pub trait WorldSyncExt {
fn register_sync_marker(&mut self);
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked;
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked;
fn create_entity_synced(&mut self) -> specs::EntityBuilder;
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64);
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
fn apply_entity_package<P: CompPacket>(
&mut self,
entity_package: EntityPackage<P>,
) -> specs::Entity;
fn apply_state_package<P: CompPacket>(&mut self, state_package: StatePackage<P>);
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>);
}
impl WorldSyncExt for specs::World {
fn register_sync_marker(&mut self) {
self.register_synced::<Uid>();
// TODO: Consider only having allocator server side for now
self.insert(UidAllocator::new());
}
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked,
{
self.register::<C>();
self.register_tracker::<C>();
}
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked,
{
let tracker = UpdateTracker::<C>::new(self);
self.insert(tracker);
}
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
self.create_entity().marked::<super::Uid>()
}
/// Get the UID of an entity
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid> {
self.read_storage::<Uid>().get(entity).copied()
}
/// Get the UID of an entity
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity> {
self.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid)
}
fn apply_entity_package<P: CompPacket>(
&mut self,
entity_package: EntityPackage<P>,
) -> specs::Entity {
let EntityPackage { uid, comps } = entity_package;
let entity = create_entity_with_uid(self, uid);
for packet in comps {
packet.apply_insert(entity, self)
}
entity
}
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) {
// Clear from uid allocator
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
if let Some(entity) = maybe_entity {
if let Err(err) = self.delete_entity(entity) {
error!("Failed to delete entity: {:?}", err);
}
}
}
fn apply_state_package<P: CompPacket>(&mut self, state_package: StatePackage<P>) {
let StatePackage { entities } = state_package;
// Apply state package entities
for entity_package in entities {
self.apply_entity_package(entity_package);
}
// TODO: determine if this is needed
// Initialize entities
//self.maintain();
}
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>) {
// Take ownership of the fields
let SyncPackage {
comp_updates,
created_entities,
deleted_entities,
} = package;
// Attempt to create entities
for entity_uid in created_entities {
create_entity_with_uid(self, entity_uid);
}
// Update components
for (entity_uid, update) in comp_updates {
if let Some(entity) = self
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid)
{
match update {
CompUpdateKind::Inserted(packet) => {
packet.apply_insert(entity, self);
}
CompUpdateKind::Modified(packet) => {
packet.apply_modify(entity, self);
}
CompUpdateKind::Removed(phantom) => {
P::apply_remove(phantom, entity, self);
}
}
}
}
// Attempt to delete entities that were marked for deletion
for entity_uid in deleted_entities {
self.delete_entity_and_clear_from_uid_allocator(entity_uid);
}
}
}
// Private utilities
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> specs::Entity {
let existing_entity = specs_world
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid);
match existing_entity {
Some(entity) => entity,
None => {
let entity_builder = specs_world.create_entity();
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(entity_uid));
entity_builder.with(uid).build()
}
}
}

128
common/src/sync/track.rs Normal file
View File

@ -0,0 +1,128 @@
use super::{
packet::{CompPacket, CompUpdateKind},
uid::Uid,
};
use specs::{BitSet, Component, Entity, Join, ReadStorage, World, WorldExt};
use std::{
convert::{TryFrom, TryInto},
marker::PhantomData,
};
pub struct UpdateTracker<C: Component> {
reader_id: specs::ReaderId<specs::storage::ComponentEvent>,
inserted: BitSet,
modified: BitSet,
removed: BitSet,
phantom: PhantomData<C>,
}
impl<C: Component> UpdateTracker<C>
where
C::Storage: specs::storage::Tracked,
{
pub fn new(specs_world: &mut World) -> Self {
Self {
reader_id: specs_world.write_storage::<C>().register_reader(),
inserted: BitSet::new(),
modified: BitSet::new(),
removed: BitSet::new(),
phantom: PhantomData,
}
}
pub fn inserted(&self) -> &BitSet {
&self.inserted
}
pub fn modified(&self) -> &BitSet {
&self.modified
}
pub fn removed(&self) -> &BitSet {
&self.removed
}
pub fn record_changes<'a>(&mut self, storage: &specs::ReadStorage<'a, C>) {
self.inserted.clear();
self.modified.clear();
self.removed.clear();
for event in storage.channel().read(&mut self.reader_id) {
match event {
specs::storage::ComponentEvent::Inserted(id) => {
// If previously removed/modified we don't need to know that anymore
self.removed.remove(*id);
self.modified.remove(*id);
self.inserted.add(*id);
}
specs::storage::ComponentEvent::Modified(id) => {
// We don't care about modification if the component was just added
if !self.inserted.contains(*id) {
debug_assert!(!self.removed.contains(*id)); // Theoretically impossible
self.modified.add(*id);
}
}
specs::storage::ComponentEvent::Removed(id) => {
// Don't need to know that it was inserted/modified if it was subsequently removed
self.inserted.remove(*id);
self.modified.remove(*id);
self.removed.add(*id);
}
};
}
}
}
impl<C: Component + Clone + Send + Sync> UpdateTracker<C> {
pub fn add_packet_for<'a, P>(
&self,
storage: &ReadStorage<'a, C>,
entity: Entity,
packets: &mut Vec<P>,
) where
P: CompPacket,
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
if let Some(comp) = storage.get(entity) {
packets.push(P::from(comp.clone()));
}
}
pub fn get_updates_for<'a, P>(
&self,
uids: &specs::ReadStorage<'a, Uid>,
storage: &specs::ReadStorage<'a, C>,
entity_filter: impl Join + Copy,
buf: &mut Vec<(u64, CompUpdateKind<P>)>,
) where
P: CompPacket,
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
// Generate inserted updates
for (uid, comp, _, _) in (uids, storage, &self.inserted, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Inserted(P::from(comp.clone())),
));
}
// Generate modified updates
for (uid, comp, _, _) in (uids, storage, &self.modified, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Modified(P::from(comp.clone())),
));
}
// Generate removed updates
for (uid, _, _) in (uids, &self.removed, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>)),
));
}
}
}

92
common/src/sync/uid.rs Normal file
View File

@ -0,0 +1,92 @@
use serde_derive::{Deserialize, Serialize};
use specs::{
saveload::{Marker, MarkerAllocator},
world::EntitiesRes,
Component, Entity, FlaggedStorage, Join, ReadStorage, VecStorage,
};
use std::{collections::HashMap, fmt, u64};
#[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 From<u64> for Uid {
fn from(uid: u64) -> Self {
Self(uid)
}
}
impl fmt::Display for Uid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Component for Uid {
type Storage = FlaggedStorage<Self, 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 {
index: u64,
mapping: HashMap<u64, Entity>,
}
impl UidAllocator {
pub fn new() -> Self {
Self {
index: 0,
mapping: HashMap::new(),
}
}
// Useful for when a single entity is deleted because it doesn't reconstruct the entire hashmap
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> {
self.mapping.remove(&id)
}
}
impl Default for UidAllocator {
fn default() -> Self {
Self::new()
}
}
impl MarkerAllocator<Uid> for UidAllocator {
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
let id = id.unwrap_or_else(|| {
let id = self.index;
self.index += 1;
id
});
self.mapping.insert(id, entity);
Uid(id)
}
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
self.mapping.get(&id).copied()
}
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
self.mapping = (entities, storage)
.join()
.map(|(e, m)| (m.id(), e))
.collect();
}
}

View File

@ -1,6 +1,5 @@
use crate::comp::{ use crate::comp::{
Agent, CharacterState, Controller, ControllerInputs, MountState, MovementState::Glide, Pos, Agent, CharacterState, Controller, MountState, MovementState::Glide, Pos, Stats,
Stats,
}; };
use crate::pathfinding::WorldPath; use crate::pathfinding::WorldPath;
use crate::terrain::TerrainGrid; use crate::terrain::TerrainGrid;
@ -60,7 +59,7 @@ impl<'a> System<'a> for Sys {
controller.reset(); controller.reset();
let mut inputs = ControllerInputs::default(); let mut inputs = &mut controller.inputs;
match agent { match agent {
Agent::Traveler { path } => { Agent::Traveler { path } => {
@ -147,10 +146,13 @@ impl<'a> System<'a> for Sys {
let dist = Vec2::<f32>::from(target_pos.0 - pos.0).magnitude(); let dist = Vec2::<f32>::from(target_pos.0 - pos.0).magnitude();
if target_stats.is_dead { if target_stats.is_dead {
choose_new = true; choose_new = true;
} else if dist < MIN_ATTACK_DIST } else if dist < 0.001 {
&& dist > 0.001 // Probably can only happen when entities are at a different z-level
&& rand::random::<f32>() < 0.3 // since at the same level repulsion would keep them apart.
{ // Distinct from the first if block since we may want to change the
// behavior for this case.
choose_new = true;
} else if dist < MIN_ATTACK_DIST {
// Fight (and slowly move closer) // Fight (and slowly move closer)
inputs.move_dir = inputs.move_dir =
Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.01; Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.01;
@ -203,7 +205,8 @@ impl<'a> System<'a> for Sys {
} }
} }
controller.inputs = inputs; debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
} }
} }
} }

View File

@ -1,10 +1,11 @@
use crate::{ use crate::{
comp::{ comp::{
ActionState::*, CharacterState, Controller, HealthChange, HealthSource, Item, ItemKind, ActionState::*, Body, CharacterState, Controller, HealthChange, HealthSource, Item,
Ori, Pos, Stats, ItemKind, Ori, Pos, Scale, Stats,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
state::{DeltaTime, Uid}, state::DeltaTime,
sync::Uid,
}; };
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::time::Duration; use std::time::Duration;
@ -12,7 +13,8 @@ use vek::*;
const BLOCK_EFFICIENCY: f32 = 0.9; const BLOCK_EFFICIENCY: f32 = 0.9;
const ATTACK_RANGE: f32 = 4.0; const ATTACK_RANGE: f32 = 3.5;
const ATTACK_ANGLE: f32 = 45.0;
const BLOCK_ANGLE: f32 = 180.0; const BLOCK_ANGLE: f32 = 180.0;
/// This system is responsible for handling accepted inputs like moving or attacking /// This system is responsible for handling accepted inputs like moving or attacking
@ -26,9 +28,11 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>, ReadStorage<'a, Ori>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Controller>, ReadStorage<'a, Controller>,
ReadStorage<'a, Body>,
ReadStorage<'a, Stats>,
WriteStorage<'a, CharacterState>, WriteStorage<'a, CharacterState>,
WriteStorage<'a, Stats>,
); );
fn run( fn run(
@ -41,20 +45,23 @@ impl<'a> System<'a> for Sys {
uids, uids,
positions, positions,
orientations, orientations,
scales,
controllers, controllers,
mut character_states, bodies,
stats, stats,
mut character_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
let mut _local_emitter = local_bus.emitter(); let mut _local_emitter = local_bus.emitter();
// Attacks // Attacks
for (entity, uid, pos, ori, _, stat) in ( for (entity, uid, pos, ori, scale_maybe, _, attacker_stats) in (
&entities, &entities,
&uids, &uids,
&positions, &positions,
&orientations, &orientations,
scales.maybe(),
&controllers, &controllers,
&stats, &stats,
) )
@ -63,7 +70,7 @@ impl<'a> System<'a> for Sys {
let recover_duration = if let Some(Item { let recover_duration = if let Some(Item {
kind: ItemKind::Tool { kind, .. }, kind: ItemKind::Tool { kind, .. },
.. ..
}) = stat.equipment.main }) = attacker_stats.equipment.main
{ {
kind.attack_recover_duration() kind.attack_recover_duration()
} else { } else {
@ -91,13 +98,15 @@ impl<'a> System<'a> for Sys {
if deal_damage { if deal_damage {
if let Some(Attack { .. }) = &character_states.get(entity).map(|c| c.action) { if let Some(Attack { .. }) = &character_states.get(entity).map(|c| c.action) {
// Go through all other entities // Go through all other entities
for (b, uid_b, pos_b, ori_b, character_b, stat_b) in ( for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
&entities, &entities,
&uids, &uids,
&positions, &positions,
&orientations, &orientations,
scales.maybe(),
&character_states, &character_states,
&stats, &stats,
&bodies,
) )
.join() .join()
{ {
@ -106,16 +115,21 @@ impl<'a> System<'a> for Sys {
let pos_b2: Vec2<f32> = Vec2::from(pos_b.0); let pos_b2: Vec2<f32> = Vec2::from(pos_b.0);
let ori2 = Vec2::from(ori.0); let ori2 = Vec2::from(ori.0);
// Scales
let scale = scale_maybe.map_or(1.0, |s| s.0);
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
let rad_b = body_b.radius() * scale_b;
// Check if it is a hit // Check if it is a hit
if entity != b if entity != b
&& !stat_b.is_dead && !stats_b.is_dead
&& pos.0.distance_squared(pos_b.0) < ATTACK_RANGE.powi(2) // Spherical wedge shaped attack field
// TODO: Use size instead of 1.0 && pos.0.distance_squared(pos_b.0) < (rad_b + scale * ATTACK_RANGE).powi(2)
&& ori2.angle_between(pos_b2 - pos2) < (2.0 / pos2.distance(pos_b2)).atan() && ori2.angle_between(pos_b2 - pos2) < ATTACK_ANGLE.to_radians() / 2.0 + (rad_b / pos2.distance(pos_b2)).atan()
{ {
// Weapon gives base damage // Weapon gives base damage
let mut dmg = if let Some(ItemKind::Tool { power, .. }) = let mut dmg = if let Some(ItemKind::Tool { power, .. }) =
stat.equipment.main.as_ref().map(|i| &i.kind) attacker_stats.equipment.main.as_ref().map(|i| &i.kind)
{ {
*power as i32 *power as i32
} else { } else {
@ -124,8 +138,8 @@ impl<'a> System<'a> for Sys {
// Block // Block
if character_b.action.is_block() if character_b.action.is_block()
&& ori_b.0.angle_between(pos.0 - pos_b.0).to_degrees() && ori_b.0.angle_between(pos.0 - pos_b.0)
< BLOCK_ANGLE / 2.0 < BLOCK_ANGLE.to_radians() / 2.0
{ {
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as i32 dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as i32
} }

View File

@ -7,12 +7,12 @@ use crate::{
}, },
event::{Emitter, EventBus, LocalEvent, ServerEvent}, event::{Emitter, EventBus, LocalEvent, ServerEvent},
state::DeltaTime, state::DeltaTime,
sync::{Uid, UidAllocator},
}; };
use specs::{ use specs::{
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
Entities, Entity, Join, Read, ReadStorage, System, WriteStorage, Entities, Entity, Join, Read, ReadStorage, System, WriteStorage,
}; };
use sphynx::{Uid, UidAllocator};
use std::time::Duration; use std::time::Duration;
use vek::*; use vek::*;

View File

@ -2,6 +2,7 @@ pub mod agent;
mod cleanup; mod cleanup;
pub mod combat; pub mod combat;
pub mod controller; pub mod controller;
mod mount;
pub mod movement; pub mod movement;
pub mod phys; pub mod phys;
mod projectile; mod projectile;
@ -13,6 +14,7 @@ use specs::DispatcherBuilder;
// System names // System names
const AGENT_SYS: &str = "agent_sys"; const AGENT_SYS: &str = "agent_sys";
const CONTROLLER_SYS: &str = "controller_sys"; const CONTROLLER_SYS: &str = "controller_sys";
const MOUNT_SYS: &str = "mount_sys";
const PHYS_SYS: &str = "phys_sys"; const PHYS_SYS: &str = "phys_sys";
const MOVEMENT_SYS: &str = "movement_sys"; const MOVEMENT_SYS: &str = "movement_sys";
const PROJECTILE_SYS: &str = "projectile_sys"; const PROJECTILE_SYS: &str = "projectile_sys";
@ -23,13 +25,20 @@ const CLEANUP_SYS: &str = "cleanup_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]); dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
dispatch_builder.add(mount::Sys, MOUNT_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[]); dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[]);
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]); dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]); dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
dispatch_builder.add( dispatch_builder.add(
phys::Sys, phys::Sys,
PHYS_SYS, PHYS_SYS,
&[CONTROLLER_SYS, MOVEMENT_SYS, COMBAT_SYS, STATS_SYS], &[
CONTROLLER_SYS,
MOUNT_SYS,
MOVEMENT_SYS,
COMBAT_SYS,
STATS_SYS,
],
); );
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]); dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
dispatch_builder.add(cleanup::Sys, CLEANUP_SYS, &[PHYS_SYS]); dispatch_builder.add(cleanup::Sys, CLEANUP_SYS, &[PHYS_SYS]);

78
common/src/sys/mount.rs Normal file
View File

@ -0,0 +1,78 @@
use crate::{
comp::{Controller, MountState, Mounting, Ori, Pos, Vel},
sync::UidAllocator,
};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, System, WriteStorage,
};
use vek::*;
/// This system is responsible for controlling mounts
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>,
WriteStorage<'a, Controller>,
WriteStorage<'a, MountState>,
WriteStorage<'a, Mounting>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
);
fn run(
&mut self,
(
uid_allocator,
entities,
mut controllers,
mut mount_state,
mut mountings,
mut positions,
mut velocities,
mut orientations,
): Self::SystemData,
) {
// Mounted entities.
for (entity, mut mount_states) in (&entities, &mut mount_state.restrict_mut()).join() {
match mount_states.get_unchecked() {
MountState::Unmounted => {}
MountState::MountedBy(mounter_uid) => {
if let Some((controller, mounter)) = uid_allocator
.retrieve_entity_internal(mounter_uid.id())
.and_then(|mounter| controllers.get(mounter).cloned().map(|x| (x, mounter)))
{
// TODO: consider joining on these? (remember we can use .maybe())
let pos = positions.get(entity).copied();
let ori = orientations.get(entity).copied();
let vel = velocities.get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let _ = positions.insert(mounter, Pos(pos.0 + Vec3::unit_z() * 1.0));
let _ = orientations.insert(mounter, ori);
let _ = velocities.insert(mounter, vel);
}
let _ = controllers.insert(entity, controller);
} else {
*(mount_states.get_mut_unchecked()) = MountState::Unmounted;
}
}
}
}
let mut to_unmount = Vec::new();
for (entity, Mounting(mountee_uid)) in (&entities, &mountings).join() {
if uid_allocator
.retrieve_entity_internal(mountee_uid.id())
.filter(|mountee| entities.is_alive(*mountee))
.is_none()
{
to_unmount.push(entity);
}
}
for entity in to_unmount {
mountings.remove(entity);
}
}
}

View File

@ -5,10 +5,10 @@ use crate::{
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
state::DeltaTime, state::DeltaTime,
sync::Uid,
terrain::TerrainGrid, terrain::TerrainGrid,
}; };
use specs::prelude::*; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use sphynx::Uid;
use std::time::Duration; use std::time::Duration;
use vek::*; use vek::*;
@ -173,10 +173,14 @@ impl<'a> System<'a> for Sys {
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 { if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 {
Vec2::from(wall_dir).normalized() Vec2::from(wall_dir).normalized()
} else { } else {
Vec2::from(vel.0) Vec2::from(inputs.move_dir)
} }
} else { } else if let Glide = character.movement {
// Note: non-gliding forces will also affect velocity and thus orientation
// producing potentially unexpected changes in direction
Vec2::from(vel.0) Vec2::from(vel.0)
} else {
Vec2::from(inputs.move_dir)
}; };
if ori_dir.magnitude_squared() > 0.0001 if ori_dir.magnitude_squared() > 0.0001

View File

@ -3,11 +3,11 @@ use {
comp::{Body, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel}, comp::{Body, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
state::DeltaTime, state::DeltaTime,
sync::Uid,
terrain::{Block, TerrainGrid}, terrain::{Block, TerrainGrid},
vol::ReadVol, vol::ReadVol,
}, },
specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}, specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage},
sphynx::Uid,
vek::*, vek::*,
}; };
@ -102,6 +102,7 @@ impl<'a> System<'a> for Sys {
let scale = scale.map(|s| s.0).unwrap_or(1.0); let scale = scale.map(|s| s.0).unwrap_or(1.0);
// Basic collision with terrain // Basic collision with terrain
// TODO: rename this, not just the player entity
let player_rad = 0.3 * scale; // half-width of the player's AABB let player_rad = 0.3 * scale; // half-width of the player's AABB
let player_height = 1.5 * scale; let player_height = 1.5 * scale;
@ -342,16 +343,20 @@ impl<'a> System<'a> for Sys {
} }
// Apply pushback // Apply pushback
for (pos, scale, mass, vel, _, _, physics) in ( for (pos, scale, mass, vel, _, _, _, physics) in (
&positions, &positions,
scales.maybe(), scales.maybe(),
masses.maybe(), masses.maybe(),
&mut velocities, &mut velocities,
&bodies, &bodies,
!&mountings, !&mountings,
stickies.maybe(),
&mut physics_states, &mut physics_states,
) )
.join() .join()
.filter(|(_, _, _, _, _, _, sticky, physics)| {
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
})
{ {
physics.touch_entity = None; physics.touch_entity = None;

View File

@ -23,8 +23,25 @@ impl<'a> System<'a> for Sys {
) { ) {
let mut server_event_emitter = server_event_bus.emitter(); let mut server_event_emitter = server_event_bus.emitter();
for (entity, mut stat) in (&entities, &mut stats).join() { // Increment last change timer
if stat.should_die() && !stat.is_dead { stats.set_event_emission(false); // avoid unnecessary syncing
for stat in (&mut stats).join() {
stat.health.last_change.0 += f64::from(dt.0);
}
stats.set_event_emission(true);
// Mutates all stats every tick causing the server to resend this component for every entity every tick
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
let (set_dead, level_up) = {
let stat = stats.get_unchecked();
(
stat.should_die() && !stat.is_dead,
stat.exp.current() >= stat.exp.maximum(),
)
};
if set_dead {
let stat = stats.get_mut_unchecked();
server_event_emitter.emit(ServerEvent::Destroy { server_event_emitter.emit(ServerEvent::Destroy {
entity, entity,
cause: stat.health.last_change.1.cause, cause: stat.health.last_change.1.cause,
@ -33,9 +50,8 @@ impl<'a> System<'a> for Sys {
stat.is_dead = true; stat.is_dead = true;
} }
stat.health.last_change.0 += f64::from(dt.0); if level_up {
let stat = stats.get_mut_unchecked();
if stat.exp.current() >= stat.exp.maximum() {
while stat.exp.current() >= stat.exp.maximum() { while stat.exp.current() >= stat.exp.maximum() {
stat.exp.change_by(-(stat.exp.maximum() as i64)); stat.exp.change_by(-(stat.exp.maximum() as i64));
stat.exp.change_maximum_by(25); stat.exp.change_maximum_by(25);

View File

@ -11,7 +11,7 @@ world = { package = "veloren-world", path = "../world" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
log = "0.4.8" log = "0.4.8"
specs = "0.14.2" specs = { version = "0.15.1", features = ["shred-derive"] }
vek = "0.9.9" vek = "0.9.9"
uvth = "3.1.1" uvth = "3.1.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
@ -27,5 +27,3 @@ prometheus = "0.7"
prometheus-static-metric = "0.2" prometheus-static-metric = "0.2"
rouille = "3.0.0" rouille = "3.0.0"
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" } portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
# TODO: remove when upgrading to specs 0.15
hibitset = "0.5.3"

View File

@ -11,15 +11,17 @@ use common::{
npc::{get_npc_name, NpcKind}, npc::{get_npc_name, NpcKind},
pathfinding::WorldPath, pathfinding::WorldPath,
state::TimeOfDay, state::TimeOfDay,
sync::WorldSyncExt,
terrain::{Block, BlockKind, TerrainChunkSize}, terrain::{Block, BlockKind, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
}; };
use rand::Rng; use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join}; use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use vek::*; use vek::*;
use world::util::Sampler; use world::util::Sampler;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::error;
use scan_fmt::{scan_fmt, scan_fmt_some}; use scan_fmt::{scan_fmt, scan_fmt_some};
/// Struct representing a command that a user can run from server chat. /// Struct representing a command that a user can run from server chat.
@ -1117,7 +1119,9 @@ fn handle_remove_lights(
let size = to_delete.len(); let size = to_delete.len();
for entity in to_delete { for entity in to_delete {
let _ = server.state.ecs_mut().delete_entity_synced(entity); if let Err(err) = server.state.delete_entity_recorded(entity) {
error!("Failed to delete light: {:?}", err);
}
} }
server.notify_client( server.notify_client(

View File

@ -19,6 +19,7 @@ use crate::{
chunk_generator::ChunkGenerator, chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription}, client::{Client, RegionSubscription},
cmd::CHAT_COMMANDS, cmd::CHAT_COMMANDS,
sys::sentinel::{DeletedEntities, TrackedComps},
}; };
use common::{ use common::{
assets, comp, assets, comp,
@ -26,14 +27,18 @@ use common::{
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg}, msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
net::PostOffice, net::PostOffice,
state::{BlockChange, State, TimeOfDay, Uid}, state::{BlockChange, State, TimeOfDay},
sync::{Uid, WorldSyncExt},
terrain::{block::Block, TerrainChunkSize, TerrainGrid}, terrain::{block::Block, TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize, Vox}, vol::{ReadVol, RectVolSize, Vox},
}; };
use log::debug; use log::{debug, error};
use metrics::ServerMetrics; use metrics::ServerMetrics;
use rand::Rng; use rand::Rng;
use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity}; use specs::{
join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow,
SystemData, WorldExt,
};
use std::{ use std::{
i32, i32,
sync::Arc, sync::Arc,
@ -83,25 +88,18 @@ impl Server {
/// Create a new `Server` /// Create a new `Server`
pub fn new(settings: ServerSettings) -> Result<Self, Error> { pub fn new(settings: ServerSettings) -> Result<Self, Error> {
let mut state = State::default(); let mut state = State::default();
state state.ecs_mut().insert(EventBus::<ServerEvent>::default());
.ecs_mut()
.add_resource(EventBus::<ServerEvent>::default());
// TODO: anything but this // TODO: anything but this
state.ecs_mut().add_resource(AuthProvider::new()); state.ecs_mut().insert(AuthProvider::new());
state.ecs_mut().add_resource(Tick(0)); state.ecs_mut().insert(Tick(0));
state.ecs_mut().add_resource(ChunkGenerator::new()); state.ecs_mut().insert(ChunkGenerator::new());
// System timers // System timers for performance monitoring
state state.ecs_mut().insert(sys::EntitySyncTimer::default());
.ecs_mut() state.ecs_mut().insert(sys::MessageTimer::default());
.add_resource(sys::EntitySyncTimer::default()); state.ecs_mut().insert(sys::SentinelTimer::default());
state.ecs_mut().add_resource(sys::MessageTimer::default()); state.ecs_mut().insert(sys::SubscriptionTimer::default());
state state.ecs_mut().insert(sys::TerrainSyncTimer::default());
.ecs_mut() state.ecs_mut().insert(sys::TerrainTimer::default());
.add_resource(sys::SubscriptionTimer::default());
state
.ecs_mut()
.add_resource(sys::TerrainSyncTimer::default());
state.ecs_mut().add_resource(sys::TerrainTimer::default());
// Server-only components // Server-only components
state.ecs_mut().register::<RegionSubscription>(); state.ecs_mut().register::<RegionSubscription>();
state.ecs_mut().register::<Client>(); state.ecs_mut().register::<Client>();
@ -153,11 +151,16 @@ impl Server {
}; };
// set the spawn point we calculated above // set the spawn point we calculated above
state.ecs_mut().add_resource(SpawnPoint(spawn_point)); state.ecs_mut().insert(SpawnPoint(spawn_point));
// Set starting time for the server. // Set starting time for the server.
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time; state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;
// Register trackers
sys::sentinel::register_trackers(&mut state.ecs_mut());
state.ecs_mut().insert(DeletedEntities::default());
let this = Self { let this = Self {
state, state,
world: Arc::new(world), world: Arc::new(world),
@ -301,8 +304,6 @@ impl Server {
let server_settings = &self.server_settings; let server_settings = &self.server_settings;
let mut todo_remove = None;
match event { match event {
ServerEvent::Explosion { pos, radius } => { ServerEvent::Explosion { pos, radius } => {
const RAYS: usize = 500; const RAYS: usize = 500;
@ -372,19 +373,20 @@ impl Server {
} }
ServerEvent::Destroy { entity, cause } => { ServerEvent::Destroy { entity, cause } => {
let ecs = state.ecs();
// Chat message // Chat message
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) { if let Some(player) = state.ecs().read_storage::<comp::Player>().get(entity) {
let msg = if let comp::HealthSource::Attack { by } = cause { let msg = if let comp::HealthSource::Attack { by } = cause {
ecs.entity_from_uid(by.into()).and_then(|attacker| { state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
ecs.read_storage::<comp::Player>().get(attacker).map( state
|attacker_alias| { .ecs()
.read_storage::<comp::Player>()
.get(attacker)
.map(|attacker_alias| {
format!( format!(
"{} was killed by {}", "{} was killed by {}",
&player.alias, &attacker_alias.alias &player.alias, &attacker_alias.alias
) )
}, })
)
}) })
} else { } else {
None None
@ -394,28 +396,48 @@ impl Server {
state.notify_registered_clients(ServerMsg::kill(msg)); state.notify_registered_clients(ServerMsg::kill(msg));
} }
// Give EXP to the killer if entity had stats {
let mut stats = ecs.write_storage::<comp::Stats>(); // Give EXP to the killer if entity had stats
let mut stats = state.ecs().write_storage::<comp::Stats>();
if let Some(entity_stats) = stats.get(entity).cloned() { if let Some(entity_stats) = stats.get(entity).cloned() {
if let comp::HealthSource::Attack { by } = cause { if let comp::HealthSource::Attack { by } = cause {
ecs.entity_from_uid(by.into()).map(|attacker| { state.ecs().entity_from_uid(by.into()).map(|attacker| {
if let Some(attacker_stats) = stats.get_mut(attacker) { if let Some(attacker_stats) = stats.get_mut(attacker) {
// TODO: Discuss whether we should give EXP by Player Killing or not. // TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats attacker_stats
.exp .exp
.change_by((entity_stats.level.level() * 10) as i64); .change_by((entity_stats.level.level() * 10) as i64);
} }
}); });
}
} }
} }
if let Some(client) = ecs.write_storage::<Client>().get_mut(entity) { let mut remove = true;
let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero()));
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate); if let Some(client) = state.ecs().write_storage::<Client>().get_mut(entity) {
remove = false;
state
.ecs()
.write_storage()
.insert(entity, comp::Vel(Vec3::zero()))
.err()
.map(|err| error!("Failed to set zero vel on dead client: {:?}", err));
state
.ecs()
.write_storage()
.insert(entity, comp::ForceUpdate)
.err()
.map(|err| {
error!("Failed to insert ForceUpdate on dead client: {:?}", err)
});
client.force_state(ClientState::Dead); client.force_state(ClientState::Dead);
} else { }
todo_remove = Some(entity.clone());
if remove {
if let Err(err) = state.delete_entity_recorded(entity) {
error!("Failed to delete destroyed entity: {:?}", err);
}
} }
} }
@ -449,7 +471,9 @@ impl Server {
}; };
if let Some(item_entity) = item_entity { if let Some(item_entity) = item_entity {
let _ = state.ecs_mut().delete_entity_synced(item_entity); if let Err(err) = state.delete_entity_recorded(item_entity) {
error!("Failed to delete picked up item entity: {:?}", err);
}
} }
state.write_component(entity, comp::InventoryUpdate); state.write_component(entity, comp::InventoryUpdate);
@ -605,8 +629,8 @@ impl Server {
{ {
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
.ecs() .ecs()
.write_storage::<comp::MountState>() .read_storage::<comp::MountState>()
.get_mut(mountee) .get(mountee)
.cloned() .cloned()
{ {
true true
@ -720,7 +744,7 @@ impl Server {
main, main,
&server_settings, &server_settings,
); );
Self::initialize_region_subscription(state, entity); sys::subscription::initialize_region_subscription(state.ecs(), entity);
} }
ServerEvent::CreateNpc { ServerEvent::CreateNpc {
@ -738,8 +762,8 @@ impl Server {
} }
ServerEvent::ClientDisconnect(entity) => { ServerEvent::ClientDisconnect(entity) => {
if let Err(err) = state.ecs_mut().delete_entity_synced(entity) { if let Err(err) = state.delete_entity_recorded(entity) {
debug!("Failed to delete disconnected client: {:?}", err); error!("Failed to delete disconnected client: {:?}", err);
} }
frontend_events.push(Event::ClientDisconnected { entity }); frontend_events.push(Event::ClientDisconnected { entity });
@ -753,11 +777,6 @@ impl Server {
chat_commands.push((entity, cmd)); chat_commands.push((entity, cmd));
} }
} }
// TODO: is this needed?
if let Some(entity) = todo_remove {
let _ = state.ecs_mut().delete_entity_synced(entity);
}
} }
// Generate requested chunks. // Generate requested chunks.
@ -816,8 +835,12 @@ impl Server {
// 3) Handle inputs from clients // 3) Handle inputs from clients
frontend_events.append(&mut self.handle_new_connections()?); frontend_events.append(&mut self.handle_new_connections()?);
// Run message recieving sys before the systems in common for decreased latency (e.g. run before controller system)
sys::message::Sys.run_now(&self.state.ecs());
let before_tick_4 = Instant::now(); let before_tick_4 = Instant::now();
// 4) Tick the client's LocalState.
// 4) Tick the server's LocalState.
self.state.tick(dt, sys::add_server_systems); self.state.tick(dt, sys::add_server_systems);
let before_handle_events = Instant::now(); let before_handle_events = Instant::now();
@ -832,11 +855,6 @@ impl Server {
let before_tick_6 = Instant::now(); let before_tick_6 = Instant::now();
// 6) Synchronise clients with the new state of the world. // 6) Synchronise clients with the new state of the world.
// TODO: Remove sphynx
// Sync 'logical' state using Sphynx.
let sync_package = self.state.ecs_mut().next_sync_package();
self.state
.notify_registered_clients(ServerMsg::EcsSync(sync_package));
// Remove NPCs that are outside the view distances of all players // Remove NPCs that are outside the view distances of all players
// This is done by removing NPCs in unloaded chunks // This is done by removing NPCs in unloaded chunks
@ -853,7 +871,9 @@ impl Server {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
for entity in to_delete { for entity in to_delete {
let _ = self.state.ecs_mut().delete_entity_synced(entity); if let Err(err) = self.state.delete_entity_recorded(entity) {
error!("Failed to delete agent outside the terrain: {:?}", err);
}
} }
let before_tick_7 = Instant::now(); let before_tick_7 = Instant::now();
@ -864,6 +884,7 @@ impl Server {
.read_resource::<sys::EntitySyncTimer>() .read_resource::<sys::EntitySyncTimer>()
.nanos as i64; .nanos as i64;
let message_nanos = self.state.ecs().read_resource::<sys::MessageTimer>().nanos as i64; let message_nanos = self.state.ecs().read_resource::<sys::MessageTimer>().nanos as i64;
let sentinel_nanos = self.state.ecs().read_resource::<sys::SentinelTimer>().nanos as i64;
let subscription_nanos = self let subscription_nanos = self
.state .state
.ecs() .ecs()
@ -877,24 +898,28 @@ impl Server {
let terrain_nanos = self.state.ecs().read_resource::<sys::TerrainTimer>().nanos as i64; let terrain_nanos = self.state.ecs().read_resource::<sys::TerrainTimer>().nanos as i64;
let total_sys_nanos = entity_sync_nanos let total_sys_nanos = entity_sync_nanos
+ message_nanos + message_nanos
+ sentinel_nanos
+ subscription_nanos + subscription_nanos
+ terrain_sync_nanos + terrain_sync_nanos
+ terrain_nanos; + terrain_nanos;
self.metrics self.metrics
.tick_time .tick_time
.with_label_values(&["input"]) .with_label_values(&["input"])
.set((before_tick_4 - before_tick_1).as_nanos() as i64); .set((before_tick_4 - before_tick_1).as_nanos() as i64 - message_nanos);
self.metrics self.metrics
.tick_time .tick_time
.with_label_values(&["state tick"]) .with_label_values(&["state tick"])
.set((before_handle_events - before_tick_4).as_nanos() as i64 - total_sys_nanos); .set(
(before_handle_events - before_tick_4).as_nanos() as i64
- (total_sys_nanos - message_nanos),
);
self.metrics self.metrics
.tick_time .tick_time
.with_label_values(&["handle server events"]) .with_label_values(&["handle server events"])
.set((before_tick_6 - before_handle_events).as_nanos() as i64); .set((before_tick_6 - before_handle_events).as_nanos() as i64);
self.metrics self.metrics
.tick_time .tick_time
.with_label_values(&["sphynx sync"]) .with_label_values(&["entity deletion"])
.set((before_tick_7 - before_tick_6).as_nanos() as i64); .set((before_tick_7 - before_tick_6).as_nanos() as i64);
self.metrics self.metrics
.tick_time .tick_time
@ -971,7 +996,8 @@ impl Server {
.create_entity_synced() .create_entity_synced()
.with(client) .with(client)
.build(); .build();
// Return the state of the current world (all of the components that Sphynx tracks). // Send client all the tracked components currently attached to its entity as well
// as synced resources (currently only `TimeOfDay`)
log::debug!("Starting initial sync with client."); log::debug!("Starting initial sync with client.");
self.state self.state
.ecs() .ecs()
@ -979,9 +1005,11 @@ impl Server {
.get_mut(entity) .get_mut(entity)
.unwrap() .unwrap()
.notify(ServerMsg::InitialSync { .notify(ServerMsg::InitialSync {
ecs_state: self.state.ecs().gen_state_package(), // Send client their entity
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail. entity_package: TrackedComps::fetch(&self.state.ecs())
.create_entity_package(entity),
server_info: self.server_info.clone(), server_info: self.server_info.clone(),
time_of_day: *self.state.ecs().read_resource(),
// world_map: (WORLD_SIZE/*, self.world.sim().get_map()*/), // world_map: (WORLD_SIZE/*, self.world.sim().get_map()*/),
}); });
log::debug!("Done initial sync with client."); log::debug!("Done initial sync with client.");
@ -993,83 +1021,6 @@ impl Server {
Ok(frontend_events) Ok(frontend_events)
} }
/// Initialize region subscription
fn initialize_region_subscription(state: &mut State, entity: specs::Entity) {
let mut subscription = None;
if let (Some(client_pos), Some(client_vd), Some(client)) = (
state.ecs().read_storage::<comp::Pos>().get(entity),
state
.ecs()
.read_storage::<comp::Player>()
.get(entity)
.map(|pl| pl.view_distance)
.and_then(|v| v),
state.ecs().write_storage::<Client>().get_mut(entity),
) {
use common::region::RegionMap;
let fuzzy_chunk = (Vec2::<f32>::from(client_pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
let regions = common::region::regions_in_vd(
client_pos.0,
(client_vd as f32 * chunk_size) as f32
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
);
for (_, region) in state
.ecs()
.read_resource::<RegionMap>()
.iter()
.filter(|(key, _)| regions.contains(key))
{
// Sync physics of all entities in this region
for (&uid, &pos, vel, ori, character_state, _) in (
&state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position
state.ecs().read_storage::<comp::Vel>().maybe(),
state.ecs().read_storage::<comp::Ori>().maybe(),
state.ecs().read_storage::<comp::CharacterState>().maybe(),
region.entities(),
)
.join()
{
client.notify(ServerMsg::EntityPos {
entity: uid.into(),
pos,
});
if let Some(vel) = vel.copied() {
client.notify(ServerMsg::EntityVel {
entity: uid.into(),
vel,
});
}
if let Some(ori) = ori.copied() {
client.notify(ServerMsg::EntityOri {
entity: uid.into(),
ori,
});
}
if let Some(character_state) = character_state.copied() {
client.notify(ServerMsg::EntityCharacterState {
entity: uid.into(),
character_state,
});
}
}
}
subscription = Some(RegionSubscription {
fuzzy_chunk,
regions,
});
}
if let Some(subscription) = subscription {
state.write_component(entity, subscription);
}
}
pub fn notify_client(&self, entity: EcsEntity, msg: ServerMsg) { pub fn notify_client(&self, entity: EcsEntity, msg: ServerMsg) {
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) { if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.notify(msg) client.notify(msg)
@ -1131,6 +1082,10 @@ trait StateExt {
stats: comp::Stats, stats: comp::Stats,
body: comp::Body, body: comp::Body,
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration>;
} }
impl StateExt for State { impl StateExt for State {
@ -1191,4 +1146,28 @@ impl StateExt for State {
client.notify(msg.clone()) client.notify(msg.clone())
} }
} }
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
) -> Result<(), specs::error::WrongGeneration> {
let (maybe_uid, maybe_pos) = (
self.ecs().read_storage::<Uid>().get(entity).copied(),
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
);
let res = self.ecs_mut().delete_entity(entity);
if res.is_ok() {
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
let region_key = self
.ecs()
.read_resource::<common::region::RegionMap>()
.find_region(entity, pos.0)
.expect("Failed to find region containing entity during entity deletion");
self.ecs()
.write_resource::<DeletedEntities>()
.record_deleted_entity(uid, region_key);
}
}
res
}
} }

View File

@ -1,4 +1,7 @@
use super::SysTimer; use super::{
sentinel::{DeletedEntities, ReadTrackers, TrackedComps},
SysTimer,
};
use crate::{ use crate::{
client::{Client, RegionSubscription}, client::{Client, RegionSubscription},
Tick, Tick,
@ -7,7 +10,8 @@ use common::{
comp::{CharacterState, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel}, comp::{CharacterState, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel},
msg::ServerMsg, msg::ServerMsg,
region::{Event as RegionEvent, RegionMap}, region::{Event as RegionEvent, RegionMap},
state::Uid, state::TimeOfDay,
sync::Uid,
}; };
use specs::{ use specs::{
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
@ -19,6 +23,7 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
Read<'a, Tick>, Read<'a, Tick>,
ReadExpect<'a, TimeOfDay>,
ReadExpect<'a, RegionMap>, ReadExpect<'a, RegionMap>,
Write<'a, SysTimer<Self>>, Write<'a, SysTimer<Self>>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
@ -35,6 +40,9 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, ForceUpdate>, WriteStorage<'a, ForceUpdate>,
WriteStorage<'a, InventoryUpdate>, WriteStorage<'a, InventoryUpdate>,
Write<'a, DeletedEntities>,
TrackedComps<'a>,
ReadTrackers<'a>,
); );
fn run( fn run(
@ -42,6 +50,7 @@ impl<'a> System<'a> for Sys {
( (
entities, entities,
tick, tick,
time_of_day,
region_map, region_map,
mut timer, mut timer,
uids, uids,
@ -58,6 +67,9 @@ impl<'a> System<'a> for Sys {
mut clients, mut clients,
mut force_updates, mut force_updates,
mut inventory_updates, mut inventory_updates,
mut deleted_entities,
tracked_comps,
trackers,
): Self::SystemData, ): Self::SystemData,
) { ) {
timer.start(); timer.start();
@ -68,8 +80,8 @@ impl<'a> System<'a> for Sys {
// 2. Iterate through region subscribers (ie clients) // 2. Iterate through region subscribers (ie clients)
// - Collect a list of entity ids for clients who are subscribed to this region (hash calc to check each) // - Collect a list of entity ids for clients who are subscribed to this region (hash calc to check each)
// 3. Iterate through events from that region // 3. Iterate through events from that region
// - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity left event) // - For each entity entered event, iterate through the client list and check if they are subscribed to the source (hash calc per subscribed client per entity event), if not subscribed to the source send a entity creation message to that client
// - Do something with entity entered events when sphynx is removed?? // - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity event)
// 4. Iterate through entities in that region // 4. Iterate through entities in that region
// 5. Inform clients of the component changes for that entity // 5. Inform clients of the component changes for that entity
// - Throttle update rate base on distance to each client // - Throttle update rate base on distance to each client
@ -77,6 +89,8 @@ impl<'a> System<'a> for Sys {
// Sync physics // Sync physics
// via iterating through regions // via iterating through regions
for (key, region) in region_map.iter() { for (key, region) in region_map.iter() {
// Assemble subscriber list for this region by iterating through clients and checking
// if they are subscribed to this region
let mut subscribers = (&mut clients, &entities, &subscriptions, &positions) let mut subscribers = (&mut clients, &entities, &subscriptions, &positions)
.join() .join()
.filter_map(|(client, entity, subscription, pos)| { .filter_map(|(client, entity, subscription, pos)| {
@ -91,6 +105,10 @@ impl<'a> System<'a> for Sys {
for event in region.events() { for event in region.events() {
match event { match event {
RegionEvent::Entered(id, maybe_key) => { RegionEvent::Entered(id, maybe_key) => {
// Don't process newly created entities here (redundant network messages)
if trackers.uid.inserted().contains(*id) {
continue;
}
let entity = entities.entity(*id); let entity = entities.entity(*id);
if let Some((uid, pos, vel, ori, character_state)) = if let Some((uid, pos, vel, ori, character_state)) =
uids.get(entity).and_then(|uid| { uids.get(entity).and_then(|uid| {
@ -105,14 +123,18 @@ impl<'a> System<'a> for Sys {
}) })
}) })
{ {
let create_msg = ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity),
);
for (client, regions, client_entity, _) in &mut subscribers { for (client, regions, client_entity, _) in &mut subscribers {
if maybe_key if maybe_key
.as_ref() .as_ref()
.map(|key| !regions.contains(key)) .map(|key| !regions.contains(key))
.unwrap_or(true) .unwrap_or(true)
// Client doesn't need to know about itself
&& *client_entity != entity && *client_entity != entity
// Client doesn't need to know about itself
{ {
client.notify(create_msg.clone());
send_initial_unsynced_components( send_initial_unsynced_components(
client, client,
uid, uid,
@ -142,39 +164,59 @@ impl<'a> System<'a> for Sys {
} }
} }
let mut send_msg = // Sync tracked components
|msg: ServerMsg, entity: EcsEntity, pos: Pos, force_update, throttle: bool| { // Get deleted entities in this region from DeletedEntities
for (client, _, client_entity, client_pos) in &mut subscribers { let sync_msg = ServerMsg::EcsSync(
match force_update { trackers.create_sync_package(
None if client_entity == &entity => {} &tracked_comps,
_ => { region.entities(),
let distance_sq = client_pos.0.distance_squared(pos.0); deleted_entities
.take_deleted_in_region(key)
.unwrap_or_else(|| Vec::new()),
),
);
for (client, _, _, _) in &mut subscribers {
client.notify(sync_msg.clone());
}
// Throttle update rate based on distance to player let mut send_msg = |msg: ServerMsg,
// TODO: more entities will be farther away so it could be more entity: EcsEntity,
// efficient to reverse the order of these checks pos: Pos,
let update = if !throttle || distance_sq < 100.0f32.powi(2) { force_update: Option<&ForceUpdate>,
true // Closer than 100.0 blocks throttle: bool| {
} else if distance_sq < 150.0f32.powi(2) { for (client, _, client_entity, client_pos) in &mut subscribers {
(tick + entity.id() as u64) % 2 == 0 let update = if client_entity == &entity {
} else if distance_sq < 200.0f32.powi(2) { // Don't send client physics updates about itself unless force update is set
(tick + entity.id() as u64) % 4 == 0 force_update.is_some()
} else if distance_sq < 250.0f32.powi(2) { } else if !throttle {
(tick + entity.id() as u64) % 8 == 0 // Update rate not thottled by distance
} else if distance_sq < 300.0f32.powi(2) { true
(tick + entity.id() as u64) % 16 == 0 } else {
} else { // Throttle update rate based on distance to client
(tick + entity.id() as u64) % 32 == 0 let distance_sq = client_pos.0.distance_squared(pos.0);
}; // More entities farther away so checks start there
if distance_sq > 300.0f32.powi(2) {
if update { (tick + entity.id() as u64) % 32 == 0
client.notify(msg.clone()); } else if distance_sq > 250.0f32.powi(2) {
} (tick + entity.id() as u64) % 16 == 0
} } else if distance_sq > 200.0f32.powi(2) {
(tick + entity.id() as u64) % 8 == 0
} else if distance_sq > 150.0f32.powi(2) {
(tick + entity.id() as u64) % 4 == 0
} else if distance_sq > 100.0f32.powi(2) {
(tick + entity.id() as u64) % 2 == 0
} else {
true // Closer than 100 blocks
} }
} };
};
if update {
client.notify(msg.clone());
}
}
};
// Sync physics components
for (_, entity, &uid, &pos, maybe_vel, maybe_ori, character_state, force_update) in ( for (_, entity, &uid, &pos, maybe_vel, maybe_ori, character_state, force_update) in (
region.entities(), region.entities(),
&entities, &entities,
@ -257,6 +299,27 @@ impl<'a> System<'a> for Sys {
} }
} }
// Handle entity deletion in regions that don't exist in RegionMap (theoretically none)
for (region_key, deleted) in deleted_entities.take_remaining_deleted() {
for client in
(&mut clients, &subscriptions)
.join()
.filter_map(|(client, subscription)| {
if client.is_ingame() && subscription.regions.contains(&region_key) {
Some(client)
} else {
None
}
})
{
for uid in &deleted {
client.notify(ServerMsg::DeleteEntity(*uid));
}
}
}
// TODO: Sync clients that don't have a position?
// Sync inventories // Sync inventories
for (client, inventory, _) in (&mut clients, &inventories, &inventory_updates).join() { for (client, inventory, _) in (&mut clients, &inventories, &inventory_updates).join() {
client.notify(ServerMsg::InventoryUpdate(inventory.clone())); client.notify(ServerMsg::InventoryUpdate(inventory.clone()));
@ -266,6 +329,13 @@ impl<'a> System<'a> for Sys {
force_updates.clear(); force_updates.clear();
inventory_updates.clear(); inventory_updates.clear();
// Sync resources
// TODO: doesn't really belong in this system (rename system or create another system?)
let tof_msg = ServerMsg::TimeOfDay(*time_of_day);
for client in (&mut clients).join() {
client.notify(tof_msg.clone());
}
timer.end(); timer.end();
} }
} }

View File

@ -1,7 +1,7 @@
use super::SysTimer; use super::SysTimer;
use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{ use common::{
comp::{Admin, Body, CanBuild, Controller, Ori, Player, Pos, Vel}, comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Vel},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG}, msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG},
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}, msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
@ -25,6 +25,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, CanBuild>, ReadStorage<'a, CanBuild>,
ReadStorage<'a, Admin>, ReadStorage<'a, Admin>,
ReadStorage<'a, ForceUpdate>,
WriteExpect<'a, AuthProvider>, WriteExpect<'a, AuthProvider>,
Write<'a, BlockChange>, Write<'a, BlockChange>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
@ -46,6 +47,7 @@ impl<'a> System<'a> for Sys {
bodies, bodies,
can_build, can_build,
admins, admins,
force_updates,
mut accounts, mut accounts,
mut block_changes, mut block_changes,
mut positions, mut positions,
@ -218,9 +220,11 @@ impl<'a> System<'a> for Sys {
}, },
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state { ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
ClientState::Character => { ClientState::Character => {
let _ = positions.insert(entity, pos); if force_updates.get(entity).is_none() {
let _ = velocities.insert(entity, vel); let _ = positions.insert(entity, pos);
let _ = orientations.insert(entity, ori); let _ = velocities.insert(entity, vel);
let _ = orientations.insert(entity, ori);
}
} }
// Only characters can send positions. // Only characters can send positions.
_ => client.error_state(RequestStateError::Impossible), _ => client.error_state(RequestStateError::Impossible),

View File

@ -1,5 +1,6 @@
pub mod entity_sync; pub mod entity_sync;
pub mod message; pub mod message;
pub mod sentinel;
pub mod subscription; pub mod subscription;
pub mod terrain; pub mod terrain;
pub mod terrain_sync; pub mod terrain_sync;
@ -9,23 +10,29 @@ use std::{marker::PhantomData, time::Instant};
pub type EntitySyncTimer = SysTimer<entity_sync::Sys>; pub type EntitySyncTimer = SysTimer<entity_sync::Sys>;
pub type MessageTimer = SysTimer<message::Sys>; pub type MessageTimer = SysTimer<message::Sys>;
pub type SentinelTimer = SysTimer<sentinel::Sys>;
pub type SubscriptionTimer = SysTimer<subscription::Sys>; pub type SubscriptionTimer = SysTimer<subscription::Sys>;
pub type TerrainTimer = SysTimer<terrain::Sys>; pub type TerrainTimer = SysTimer<terrain::Sys>;
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>; pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
// System names // System names
const ENTITY_SYNC_SYS: &str = "server_entity_sync_sys"; const ENTITY_SYNC_SYS: &str = "server_entity_sync_sys";
const SENTINEL_SYS: &str = "sentinel_sys";
const SUBSCRIPTION_SYS: &str = "server_subscription_sys"; const SUBSCRIPTION_SYS: &str = "server_subscription_sys";
const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys"; const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
const TERRAIN_SYS: &str = "server_terrain_sys"; const TERRAIN_SYS: &str = "server_terrain_sys";
const MESSAGE_SYS: &str = "server_message_sys";
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
// TODO: makes some of these dependent on systems in common like the phys system
dispatch_builder.add(sentinel::Sys, SENTINEL_SYS, &[]);
dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]); dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]);
dispatch_builder.add(entity_sync::Sys, ENTITY_SYNC_SYS, &[SUBSCRIPTION_SYS]); dispatch_builder.add(
entity_sync::Sys,
ENTITY_SYNC_SYS,
&[SUBSCRIPTION_SYS, SENTINEL_SYS],
);
dispatch_builder.add(terrain_sync::Sys, TERRAIN_SYS, &[]); dispatch_builder.add(terrain_sync::Sys, TERRAIN_SYS, &[]);
dispatch_builder.add(terrain::Sys, TERRAIN_SYNC_SYS, &[TERRAIN_SYS]); dispatch_builder.add(terrain::Sys, TERRAIN_SYNC_SYS, &[TERRAIN_SYS]);
dispatch_builder.add(message::Sys, MESSAGE_SYS, &[]);
} }
/// Used to keep track of how much time each system takes /// Used to keep track of how much time each system takes

229
server/src/sys/sentinel.rs Normal file
View File

@ -0,0 +1,229 @@
use super::SysTimer;
use common::{
comp::{
Body, CanBuild, Gravity, Item, LightEmitter, Mass, MountState, Mounting, Player, Scale,
Stats, Sticky,
},
msg::EcsCompPacket,
sync::{EntityPackage, SyncPackage, Uid, UpdateTracker, WorldSyncExt},
};
use hashbrown::HashMap;
use specs::{
shred::ResourceId, Entity as EcsEntity, Join, ReadExpect, ReadStorage, System, SystemData,
World, Write, WriteExpect,
};
use vek::*;
/// Always watching
/// This system will monitor specific components for insertion, removal, and modification
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Write<'a, SysTimer<Self>>,
TrackedComps<'a>,
WriteTrackers<'a>,
);
fn run(&mut self, (mut timer, comps, mut trackers): Self::SystemData) {
timer.start();
record_changes(&comps, &mut trackers);
timer.end();
}
}
// Probably more difficult than it needs to be :p
#[derive(SystemData)]
pub struct TrackedComps<'a> {
pub uid: ReadStorage<'a, Uid>,
pub body: ReadStorage<'a, Body>,
pub player: ReadStorage<'a, Player>,
pub stats: ReadStorage<'a, Stats>,
pub can_build: ReadStorage<'a, CanBuild>,
pub light_emitter: ReadStorage<'a, LightEmitter>,
pub item: ReadStorage<'a, Item>,
pub scale: ReadStorage<'a, Scale>,
pub mounting: ReadStorage<'a, Mounting>,
pub mount_state: ReadStorage<'a, MountState>,
pub mass: ReadStorage<'a, Mass>,
pub sticky: ReadStorage<'a, Sticky>,
pub gravity: ReadStorage<'a, Gravity>,
}
impl<'a> TrackedComps<'a> {
pub fn create_entity_package(&self, entity: EcsEntity) -> EntityPackage<EcsCompPacket> {
let uid = self
.uid
.get(entity)
.copied()
.expect("No uid to create an entity package")
.0;
let mut comps = Vec::new();
self.body.get(entity).copied().map(|c| comps.push(c.into()));
self.player
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.stats
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.can_build
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.light_emitter
.get(entity)
.copied()
.map(|c| comps.push(c.into()));
self.item.get(entity).cloned().map(|c| comps.push(c.into()));
self.scale
.get(entity)
.copied()
.map(|c| comps.push(c.into()));
self.mounting
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.mount_state
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.mass.get(entity).copied().map(|c| comps.push(c.into()));
self.sticky
.get(entity)
.copied()
.map(|c| comps.push(c.into()));
self.gravity
.get(entity)
.copied()
.map(|c| comps.push(c.into()));
EntityPackage { uid, comps }
}
}
#[derive(SystemData)]
pub struct ReadTrackers<'a> {
pub uid: ReadExpect<'a, UpdateTracker<Uid>>,
pub body: ReadExpect<'a, UpdateTracker<Body>>,
pub player: ReadExpect<'a, UpdateTracker<Player>>,
pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
pub item: ReadExpect<'a, UpdateTracker<Item>>,
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
}
impl<'a> ReadTrackers<'a> {
pub fn create_sync_package(
&self,
comps: &TrackedComps,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> SyncPackage<EcsCompPacket> {
SyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities)
.with_component(&comps.uid, &*self.body, &comps.body, filter)
.with_component(&comps.uid, &*self.player, &comps.player, filter)
.with_component(&comps.uid, &*self.stats, &comps.stats, filter)
.with_component(&comps.uid, &*self.can_build, &comps.can_build, filter)
.with_component(
&comps.uid,
&*self.light_emitter,
&comps.light_emitter,
filter,
)
.with_component(&comps.uid, &*self.item, &comps.item, filter)
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
.with_component(&comps.uid, &*self.gravity, &comps.gravity, filter)
}
}
#[derive(SystemData)]
pub struct WriteTrackers<'a> {
uid: WriteExpect<'a, UpdateTracker<Uid>>,
body: WriteExpect<'a, UpdateTracker<Body>>,
player: WriteExpect<'a, UpdateTracker<Player>>,
stats: WriteExpect<'a, UpdateTracker<Stats>>,
can_build: WriteExpect<'a, UpdateTracker<CanBuild>>,
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
item: WriteExpect<'a, UpdateTracker<Item>>,
scale: WriteExpect<'a, UpdateTracker<Scale>>,
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
mass: WriteExpect<'a, UpdateTracker<Mass>>,
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
gravity: WriteExpect<'a, UpdateTracker<Gravity>>,
}
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
// Update trackers
trackers.uid.record_changes(&comps.uid);
trackers.body.record_changes(&comps.body);
trackers.player.record_changes(&comps.player);
trackers.stats.record_changes(&comps.stats);
trackers.can_build.record_changes(&comps.can_build);
trackers.light_emitter.record_changes(&comps.light_emitter);
trackers.item.record_changes(&comps.item);
trackers.scale.record_changes(&comps.scale);
trackers.mounting.record_changes(&comps.mounting);
trackers.mount_state.record_changes(&comps.mount_state);
trackers.mass.record_changes(&comps.mass);
trackers.sticky.record_changes(&comps.sticky);
trackers.gravity.record_changes(&comps.gravity);
}
pub fn register_trackers(world: &mut World) {
world.register_tracker::<Uid>();
world.register_tracker::<Body>();
world.register_tracker::<Player>();
world.register_tracker::<Stats>();
world.register_tracker::<CanBuild>();
world.register_tracker::<LightEmitter>();
world.register_tracker::<Item>();
world.register_tracker::<Scale>();
world.register_tracker::<Mounting>();
world.register_tracker::<MountState>();
world.register_tracker::<Mass>();
world.register_tracker::<Sticky>();
world.register_tracker::<Gravity>();
}
/// Deleted entities grouped by region
pub struct DeletedEntities {
map: HashMap<Vec2<i32>, Vec<u64>>,
}
impl Default for DeletedEntities {
fn default() -> Self {
Self {
map: HashMap::new(),
}
}
}
impl DeletedEntities {
pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2<i32>) {
self.map
.entry(region_key)
.or_insert(Vec::new())
.push(uid.into());
}
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Option<Vec<u64>> {
self.map.remove(&key)
}
pub fn get_deleted_in_region(&mut self, key: Vec2<i32>) -> Option<&Vec<u64>> {
self.map.get(&key)
}
pub fn take_remaining_deleted(&mut self) -> Vec<(Vec2<i32>, Vec<u64>)> {
// TODO: don't allocate
self.map.drain().collect()
}
}

View File

@ -1,14 +1,21 @@
use super::SysTimer; use super::{
sentinel::{DeletedEntities, TrackedComps},
SysTimer,
};
use crate::client::{self, Client, RegionSubscription}; use crate::client::{self, Client, RegionSubscription};
use common::{ use common::{
comp::{CharacterState, Ori, Player, Pos, Vel}, comp::{CharacterState, Ori, Player, Pos, Vel},
msg::ServerMsg, msg::ServerMsg,
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
state::Uid, sync::Uid,
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
vol::RectVolSize, vol::RectVolSize,
}; };
use specs::{Entities, Join, ReadExpect, ReadStorage, System, Write, WriteStorage}; use log::{debug, error};
use specs::{
Entities, Join, ReadExpect, ReadStorage, System, SystemData, World, WorldExt, Write,
WriteStorage,
};
use vek::*; use vek::*;
/// This system will update region subscriptions based on client positions /// This system will update region subscriptions based on client positions
@ -26,6 +33,8 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Player>, ReadStorage<'a, Player>,
WriteStorage<'a, Client>, WriteStorage<'a, Client>,
WriteStorage<'a, RegionSubscription>, WriteStorage<'a, RegionSubscription>,
Write<'a, DeletedEntities>,
TrackedComps<'a>,
); );
fn run( fn run(
@ -42,6 +51,8 @@ impl<'a> System<'a> for Sys {
players, players,
mut clients, mut clients,
mut subscriptions, mut subscriptions,
mut deleted_entities,
tracked_comps,
): Self::SystemData, ): Self::SystemData,
) { ) {
timer.start(); timer.start();
@ -66,8 +77,15 @@ impl<'a> System<'a> for Sys {
&entities, &entities,
) )
.join() .join()
.filter_map(|(c, s, pos, player, e)| player.view_distance.map(|v| (c, s, pos, v, e))) .filter_map(|(client, s, pos, player, e)| {
if client.is_ingame() {
player.view_distance.map(|v| (client, s, pos, v, e))
} else {
None
}
})
{ {
// Calculate current chunk
let chunk = (Vec2::<f32>::from(pos.0)) let chunk = (Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Only update regions when moving to a new chunk // Only update regions when moving to a new chunk
@ -85,7 +103,7 @@ impl<'a> System<'a> for Sys {
.reduce_or() .reduce_or()
{ {
// Update current chunk // Update current chunk
subscription.fuzzy_chunk = (Vec2::<f32>::from(pos.0)) subscription.fuzzy_chunk = Vec2::<f32>::from(pos.0)
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32); .map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Use the largest side length as our chunk size // Use the largest side length as our chunk size
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32; let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
@ -106,19 +124,24 @@ impl<'a> System<'a> for Sys {
// Iterate through regions to remove // Iterate through regions to remove
for key in regions_to_remove.drain(..) { for key in regions_to_remove.drain(..) {
// Remove region from this clients set of subscribed regions // Remove region from this client's set of subscribed regions
subscription.regions.remove(&key); subscription.regions.remove(&key);
// Tell the client to delete the entities in that region if it exists in the RegionMap // Tell the client to delete the entities in that region if it exists in the RegionMap
if let Some(region) = region_map.get(key) { if let Some(region) = region_map.get(key) {
// Process entity left events since they won't be processed during phsyics sync because this region is no longer subscribed to // Process entity left events since they won't be processed during entity sync because this region is no longer subscribed to
// TODO: consider changing system ordering??
for event in region.events() { for event in region.events() {
match event { match event {
RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway
RegionEvent::Left(id, maybe_key) => { RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity // Lookup UID for entity
// Doesn't overlap with entity deletion in sync packages
// because the uid would not be available if the entity was
// deleted
if let Some(&uid) = uids.get(entities.entity(*id)) { if let Some(&uid) = uids.get(entities.entity(*id)) {
if !maybe_key if !maybe_key
.as_ref() .as_ref()
// Don't need to check that this isn't also in the regions to remove since the entity will be removed when we get to that one
.map(|key| subscription.regions.contains(key)) .map(|key| subscription.regions.contains(key))
.unwrap_or(false) .unwrap_or(false)
{ {
@ -128,10 +151,19 @@ impl<'a> System<'a> for Sys {
} }
} }
} }
// Tell client to delete entities in the region
for (&uid, _) in (&uids, region.entities()).join() { for (&uid, _) in (&uids, region.entities()).join() {
client.notify(ServerMsg::DeleteEntity(uid.into())) client.notify(ServerMsg::DeleteEntity(uid.into()));
} }
} }
// Send deleted entities since they won't be processed for this client in entity sync
for uid in deleted_entities
.get_deleted_in_region(key)
.iter()
.flat_map(|v| v.iter())
{
client.notify(ServerMsg::DeleteEntity(*uid));
}
} }
for key in regions_in_vd( for key in regions_in_vd(
@ -139,10 +171,11 @@ impl<'a> System<'a> for Sys {
(vd as f32 * chunk_size) (vd as f32 * chunk_size)
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(), + (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
) { ) {
// Send client intial info about the entities in this region // Send client intial info about the entities in this region if it was not
if subscription.regions.insert(key) { // already within the set of subscribed regions
if subscription.regions.insert(key.clone()) {
if let Some(region) = region_map.get(key) { if let Some(region) = region_map.get(key) {
for (uid, pos, vel, ori, character_state, _, _) in ( for (uid, pos, vel, ori, character_state, _, entity) in (
&uids, &uids,
&positions, &positions,
velocities.maybe(), velocities.maybe(),
@ -154,6 +187,11 @@ impl<'a> System<'a> for Sys {
.join() .join()
.filter(|(_, _, _, _, _, _, e)| *e != client_entity) .filter(|(_, _, _, _, _, _, e)| *e != client_entity)
{ {
// Send message to create entity and tracked components
client.notify(ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity),
));
// Send message to create physics components
super::entity_sync::send_initial_unsynced_components( super::entity_sync::send_initial_unsynced_components(
client, client,
uid, uid,
@ -172,3 +210,69 @@ impl<'a> System<'a> for Sys {
timer.end(); timer.end();
} }
} }
/// Initialize region subscription
pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
if let (Some(client_pos), Some(client_vd), Some(client)) = (
world.read_storage::<Pos>().get(entity),
world
.read_storage::<Player>()
.get(entity)
.map(|pl| pl.view_distance)
.and_then(|v| v),
world.write_storage::<Client>().get_mut(entity),
) {
let fuzzy_chunk = (Vec2::<f32>::from(client_pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
let regions = common::region::regions_in_vd(
client_pos.0,
(client_vd as f32 * chunk_size) as f32
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
);
let region_map = world.read_resource::<RegionMap>();
let tracked_comps = TrackedComps::fetch(world);
for key in &regions {
if let Some(region) = region_map.get(*key) {
for (uid, pos, vel, ori, character_state, _, entity) in (
&tracked_comps.uid,
&world.read_storage::<Pos>(), // We assume all these entities have a position
world.read_storage::<Vel>().maybe(),
world.read_storage::<Ori>().maybe(),
world.read_storage::<CharacterState>().maybe(),
region.entities(),
&world.entities(),
)
.join()
{
// Send message to create entity and tracked components
client.notify(ServerMsg::CreateEntity(
tracked_comps.create_entity_package(entity),
));
// Send message to create physics components
super::entity_sync::send_initial_unsynced_components(
client,
uid,
pos,
vel,
ori,
character_state,
);
}
}
}
if let Err(err) = world.write_storage().insert(
entity,
RegionSubscription {
fuzzy_chunk,
regions,
},
) {
error!("Failed to insert region subscription component: {:?}", err);
}
} else {
debug!("Failed to initialize region subcription. Couldn't retrieve all the neccesary components on the provided entity");
}
}

View File

@ -27,7 +27,7 @@ conrod_winit = { git = "https://gitlab.com/veloren/conrod.git" }
euc = "0.3.0" euc = "0.3.0"
# ECS # ECS
specs = "0.14.2" specs = "0.15.1"
# Mathematics # Mathematics
vek = { version = "0.9.9", features = ["serde"] } vek = { version = "0.9.9", features = ["serde"] }

View File

@ -8,7 +8,7 @@ use common::{
event::{EventBus, SfxEvent, SfxEventItem}, event::{EventBus, SfxEvent, SfxEventItem},
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use specs::{Entity as EcsEntity, Join}; use specs::{Entity as EcsEntity, Join, WorldExt};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use vek::*; use vek::*;

View File

@ -9,6 +9,7 @@ use common::{
event::{EventBus, SfxEvent, SfxEventItem}, event::{EventBus, SfxEvent, SfxEventItem},
}; };
use serde::Deserialize; use serde::Deserialize;
use specs::WorldExt;
use vek::*; use vek::*;
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -7,6 +7,7 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use specs::WorldExt;
use vek::*; use vek::*;
widget_ids! { widget_ids! {

View File

@ -7,6 +7,7 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use specs::WorldExt;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use vek::*; use vek::*;

View File

@ -49,7 +49,7 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };
use specs::Join; use specs::{Join, WorldExt};
use std::collections::VecDeque; use std::collections::VecDeque;
use vek::*; use vek::*;
@ -112,6 +112,7 @@ widget_ids! {
velocity, velocity,
loaded_distance, loaded_distance,
time, time,
entity_count,
// Game Version // Game Version
version, version,
@ -872,11 +873,19 @@ impl Hud {
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.font_size(14) .font_size(14)
.set(self.ids.time, ui_widgets); .set(self.ids.time, ui_widgets);
// Number of entities
let entity_count = client.state().ecs().entities().join().count();
Text::new(&format!("Entity count: {}", entity_count))
.color(TEXT_COLOR)
.down_from(self.ids.time, 5.0)
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.entity_count, ui_widgets);
// Help Window // Help Window
Text::new("Press 'F1' to show Keybindings") Text::new("Press 'F1' to show Keybindings")
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.time, 5.0) .down_from(self.ids.entity_count, 5.0)
.font_id(self.fonts.cyri) .font_id(self.fonts.cyri)
.font_size(14) .font_size(14)
.set(self.ids.help_info, ui_widgets); .set(self.ids.help_info, ui_widgets);

View File

@ -7,7 +7,7 @@ use conrod_core::{
widget_ids, /*, Color*/ widget_ids, /*, Color*/
Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use specs::Join; use specs::{Join, WorldExt};
use client::{self, Client}; use client::{self, Client};

View File

@ -21,6 +21,7 @@ use common::{
terrain::BlockKind, terrain::BlockKind,
}; };
use log::error; use log::error;
use specs::WorldExt;
use vek::*; use vek::*;
struct Skybox { struct Skybox {

View File

@ -26,7 +26,7 @@ use common::{
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use log::trace; use log::trace;
use specs::{Entity as EcsEntity, Join}; use specs::{Entity as EcsEntity, Join, WorldExt};
use vek::*; use vek::*;
const DAMAGE_FADE_COEFFICIENT: f64 = 5.0; const DAMAGE_FADE_COEFFICIENT: f64 = 5.0;

View File

@ -22,7 +22,7 @@ use common::{
terrain::{BlockKind, TerrainChunk}, terrain::{BlockKind, TerrainChunk},
vol::ReadVol, vol::ReadVol,
}; };
use specs::Join; use specs::{Join, WorldExt};
use vek::*; use vek::*;
// TODO: Don't hard-code this. // TODO: Don't hard-code this.

View File

@ -17,7 +17,7 @@ use common::{
ChatType, ChatType,
}; };
use log::error; use log::error;
use specs::Join; use specs::{Join, WorldExt};
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
use vek::*; use vek::*;

View File

@ -342,7 +342,7 @@ impl Ui {
// moving origin to top-left corner (from middle). // moving origin to top-left corner (from middle).
let min_x = self.ui.win_w / 2.0 + l; let min_x = self.ui.win_w / 2.0 + l;
let min_y = self.ui.win_h / 2.0 - b - h; let min_y = self.ui.win_h / 2.0 - b - h;
Aabr { let intersection = Aabr {
min: Vec2 { min: Vec2 {
x: (min_x * scale_factor) as u16, x: (min_x * scale_factor) as u16,
y: (min_y * scale_factor) as u16, y: (min_y * scale_factor) as u16,
@ -352,7 +352,13 @@ impl Ui {
y: ((min_y + h) * scale_factor) as u16, y: ((min_y + h) * scale_factor) as u16,
}, },
} }
.intersection(window_scissor) .intersection(window_scissor);
if intersection.is_valid() {
intersection
} else {
Aabr::new_empty(Vec2::zero())
}
}; };
if new_scissor != current_scissor { if new_scissor != current_scissor {
// Finish the current command. // Finish the current command.
@ -750,7 +756,7 @@ impl Ui {
} }
fn default_scissor(renderer: &Renderer) -> Aabr<u16> { fn default_scissor(renderer: &Renderer) -> Aabr<u16> {
let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple(); let (screen_w, screen_h) = renderer.get_resolution().into_tuple();
Aabr { Aabr {
min: Vec2 { x: 0, y: 0 }, min: Vec2 { x: 0, y: 0 },
max: Vec2 { max: Vec2 {