mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/sync' into 'master'
Fix sync issues Closes #359 and #377 See merge request veloren/veloren!675
This commit is contained in:
commit
ddfa2b4600
431
Cargo.lock
generated
431
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"] }
|
||||||
|
@ -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 } => {
|
||||||
|
@ -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"
|
||||||
|
@ -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 {
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
@ -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>,
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
14
common/src/sync/mod.rs
Normal 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
125
common/src/sync/packet.rs
Normal 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
164
common/src/sync/sync_ext.rs
Normal 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
128
common/src/sync/track.rs
Normal 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
92
common/src/sync/uid.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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
78
common/src/sys/mount.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(®ion_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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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
229
server/src/sys/sentinel.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -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 ®ions {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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"] }
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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)]
|
||||||
|
@ -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! {
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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};
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user